OKLCH Studio · Developers

Contrast checker bookmarklet: find failing text on any page

Updated 2026-10-18

This is a bookmark that runs a contrast check on whatever page you are looking at. Click it and every piece of visible text is measured against its background using the WCAG 2.x contrast formula. Anything under the AA threshold gets a dashed red outline, and a small panel lists the worst offenders with their colours, their ratio, the ratio they needed and a link to a suggested fix.

It is built for the moment when a browser extension is not an option: a locked-down work laptop, a page behind a login, a staging site you do not want to paste into a web tool. The code is one self-contained script that makes no network request, and this page explains exactly what it measures, what it cannot, and how much to trust the numbers.

Get the bookmarklet

Drag this link to your bookmarks bar. Do not click it here; it checks the page you are on.

Colorise contrast check

4,904 characters, one line, no network requests.

Install it in your browser

A bookmarklet is a bookmark whose address is JavaScript instead of a web address. Clicking it runs that script on the current page.

  • Chrome and Edge: show the bookmarks bar with Ctrl+Shift+B (Cmd+Shift+B on a Mac), then drag the link above onto the bar.
  • Firefox: show the bookmarks toolbar with Ctrl+Shift+B, then drag the link onto it.
  • Safari on a Mac: choose View, then Show Favorites Bar, and drag the link onto the bar.
  • Safari on iPhone and iPad: dragging is not possible. Press the copy button above, bookmark any page, edit that bookmark, replace its address with the copied code and save. Then open the bookmark from the bookmarks menu on the page you want to check.

If dragging fails, use the copy button and paste the code into a new bookmark by hand. Running it twice is safe: a second click clears the first run's outlines and panel before scanning again.

What it measures, step by step

The script collects every element on the page, including inside open shadow roots, and keeps those that have text of their own: at least one direct child text node with something other than whitespace. It skips script, style, noscript, template and svg content, anything hidden by display, visibility or zero opacity, anything smaller than two pixels in either direction (which removes most screen-reader-only text) and its own panel. It stops after 5,000 text elements so a huge page cannot freeze the tab.

For each element it reads the computed text colour. If the colour has transparency, it is composited over the background. The background is found by walking up from the element through its ancestors, collecting each translucent background-color until it reaches an opaque one, then compositing the stack from the bottom up over white. White is the assumption when no ancestor is opaque, because that is the browser default.

Then it applies the formula from WCAG 2: convert each sRGB channel to linear light, weight the channels into a relative luminance, and divide the lighter luminance plus 0.05 by the darker plus 0.05. Black on white gives 21:1; #777777 on white gives 4.48:1, just under the line. The ratio is compared unrounded, as the standard intends, so 4.499:1 fails.

The threshold depends on the size of the text. Normal text must reach 4.5:1 (success criterion 1.4.3). Large text needs only 3:1, and the script treats text as large at 24 CSS pixels or more, or 18.66 pixels (14 point) or more when the computed font weight is 700 or higher.

Browsers hand back colours as rgb() or rgba() in both comma and space syntax, and the script parses those directly. For other formats, such as oklch(), color(display-p3 ...) or color-mix() results, it asks a one-pixel canvas to resolve the colour and reads the pixel back. That conversion lands in sRGB, so wide-gamut colours are clipped to the nearest displayable value, and very faint alpha values lose a little precision.

What it cannot check

Contrast depends on the pixels the reader sees, and a script that reads CSS can only approximate them. Where it gives up or can be wrong:

  • Background images and gradients. If the element or an ancestor that would supply its background has a background-image, the result is counted as *unknown*, not pass or fail. Text over photos and gradients needs a human look.
  • Text over video, canvas or images placed behind the text with positioning rather than background. The script cannot see them.
  • Overlapping and positioned layers. It walks the DOM ancestors, not the stacking order, so a modal backdrop or an absolutely positioned card over another colour is not accounted for.
  • Opacity on ancestors, filters and blend modes. A faded container is not reflected in the colours, and mix-blend-mode or filter can change what is painted.
  • Pseudo-elements such as ::before text, and text drawn into a canvas.
  • Closed shadow roots and iframes. Open shadow roots are traversed. Closed ones and the contents of iframes are not, so run the bookmarklet inside a frame's own page if you need it.
  • Dark mode and forced colours. It measures what the page renders right now, so check each theme separately. If nothing sets a background it assumes white, which is wrong for a page that relies on a dark browser default.
  • Pages that block it. Some sites send a Content Security Policy that stops javascript: URLs from running. Browsers differ on whether bookmarklets are exempt. If clicking does nothing, that is the likely reason, and the contrast checker on this site or the API is the alternative.

It uses the WCAG 2.2 thresholds, not APCA, for every pass and fail. APCA is a different model with different numbers, and WCAG is still what most policies and audits cite; the WCAG 2.2 vs APCA guide explains the difference. It does not check non-text contrast such as borders and icons; for colour-blind viewing see the colour-blindness preview.

Reading the panel

The header counts what was checked. *Pass* and *fail* are text elements measured against their threshold. *Unknown* covers text over images or gradients and colours the browser would not resolve. A page with a large unknown count is not clean; it is unmeasured.

Below the counts are the twenty failures with the lowest ratios. Each row shows the opening words of the text, the two colours as hex, the ratio floored to two decimals, and the ratio required. Clicking a row scrolls the element into view; every failing element on the page is outlined, not only the twenty listed. The close button removes the panel and restores each element's original inline outline.

Fixing what it finds

Each row has a Fix link to the contrast fix page for that exact pair. It suggests the nearest text colour that reaches AA while keeping the hue, by changing lightness in OKLCH so the colour still looks like the same colour. The page accepts any six-digit pair, so the link works for your own brand colours, not only common ones.

For a longer explanation of why lightness is the right lever and how to pick between darkening the text and lightening the background, read how to fix colour contrast. If you are choosing colours from scratch, start from the accessible palettes, where every text and background pairing is already measured.

A bookmarklet only catches problems after they ship. To stop them arriving, call the contrast API from a test or CI job and fail the build when a design token pair drops below 4.5:1. The two tools use the same WCAG formula, so a pair that fails here fails there.

Privacy and security

The script runs entirely inside the tab you clicked it on. It makes no network request, loads no other script and writes nothing to storage. The page text it reads never leaves your browser. The only link in it points to the fix page, and that is requested only if you click Fix, carrying just two hex colours, not the text.

Bookmarklets run with the full power of the page, so treat any you install the way you would an extension. The advantage here is that the code is short enough to read: press the copy button, paste it into an editor, and you can see every line. Only install bookmarklets from sources you trust.

FAQ

Does the bookmarklet send my page or its text anywhere?
No. It reads the page's styles and text inside your browser and draws the results in a panel on the same page. It makes no network request and loads no other script. Clicking a Fix link opens a new tab on colorise.online with two hex colours in the address, and nothing else.
Why does it say unknown for some text?
The text sits on a background image or gradient, or its colour was in a format the browser would not resolve. A script reading CSS cannot know which pixels are behind the text in those cases, so it reports unknown instead of guessing a pass or fail. Check those spots by eye or with a colour picker on the rendered pixels.
Why did a tool or extension report a different ratio?
Most differences come from the background. Other tools may sample the rendered pixels, ignore translucent layers or assume a different base colour. This bookmarklet composites translucent backgrounds over white and refuses to judge images and gradients. Run both on a single element and compare the two colours, not just the ratio.
Does it use WCAG or APCA?
WCAG 2.2 contrast: 4.5:1 for normal text and 3:1 for large text, on the unrounded ratio. APCA is not used for pass or fail. See the WCAG 2.2 vs APCA guide for how the two differ.
Nothing happens when I click it. What is wrong?
Usually the page's Content Security Policy or a managed-browser policy is blocking javascript: URLs, or the bookmark was saved with a damaged address. Try another page first. If other pages work, that site blocks bookmarklets and you will need the contrast checker or the contrast API instead.
Is a clean result proof the page is accessible?
No. It checks text contrast only, skips anything over images, gradients and overlays, and says nothing about focus indicators, icons, motion or colour-blind users. It is a quick triage for the most common failure, not a conformance audit.

Try it

Found a failing pair on a page? Paste the two colours into the interactive checker to see the ratio, the APCA value and a fixed colour.

Open the contrast checker

← Developers