Contrast · the WCAG 2.x ratio, worked out in your browser

Two colours, one ratio.
In paper and in dark.

Give it a text colour and the ground it sits on — hex, with or without its #, rgb(), hsl(), hwb() or any of the 148 names CSS knows — and it gives back the contrast ratio the Web Content Accessibility Guidelines define, to two decimals, against the five lines those guidelines draw: AA and AAA for body text, AA and AAA for large text, and 3:1 for the edges of controls, icons and the lines that carry meaning.

A site with a light and a dark edition has two pairs to get right, not one, so this measures both at once and paints each as a page — large text, body text with a link, a button and a hairline — in the colours you gave it. When a pair falls short it finds the nearest shade that clears the line: the same hue, only the lightness moved. See-through colours are laid on what is under them before they are measured, because that is the colour the eye gets.

Five lines checked · 148 colour names · alpha composited, never ignored · the ratio cut to two decimals, never rounded up · nothing fetched, nothing sent

The pair

One pair for paper, one for dark.

Each card is one edition’s text colour and the ground under it. Type a colour, paste one, pick one, or load one of this site’s own pairs; the ratio and the five lines follow as you type, and the address bar keeps both pairs so a link carries them. It opens on the house accent — #C2410C on white, the print red the paper edition draws its rules in, and #F97316 on the dark page.

The paper pair

Large text, 24 pixels

Body text at 16 pixels, the size most reading is done at, with a link in it.

A button: the ground’s colour on the text’s

A one-pixel hairline, above

–:1

    The dark pair

    Large text, 24 pixels

    Body text at 16 pixels, the size most reading is done at, with a link in it.

    A button: the ground’s colour on the text’s

    A one-pixel hairline, above

    –:1

      Large text is WCAG’s own definition: at least 18 point (24 CSS pixels), or 14 point (about 18.7 pixels) in bold. The 3:1 non-text line is for what a person has to see to use a thing — a field’s edge, a focus ring, an icon, a chart’s line — not for decoration, logotypes or a control that is switched off.

      The house colours

      This site’s own pairs, measured.

      The colours labs.llc sets its type in, read out of its own stylesheets and put through the same arithmetic as the cards above. Several are translucent inks — the quiet grey that eyebrows and labels are set in is the body ink at 64 percent — and they are composited on their grounds before they are measured, which is the whole reason that one is .64 and not the .55 it started as: .55 measured 3.58:1. Press a pair to load it into its card.

      Tokens, both editions

      labs.llc’s text colours, measured on their grounds in the paper and the dark edition
      What it isPaper editionDark edition

      The hairline fails 3:1 in both editions, and is meant to: a rule that only separates two things is decoration, which WCAG leaves out. A line that is the only sign of where a field begins is not decoration, which is why the edge of a field here is drawn at half the ink on paper and in white at .34 on dark, and measured against 3:1 in the row above it.

      Method

      What is computed, and what is not.

      The formula

      WCAG 2.x’s own, and the same three lines this site’s build gate has used on its own colours since build 520. Each 8-bit channel is divided by 255 and linearised — divided by 12.92 at or below 0.03928, otherwise ((c + 0.055) / 1.055) raised to 2.4 — and the relative luminance is 0.2126 R + 0.7152 G + 0.0722 B. The ratio is (lighter + 0.05) / (darker + 0.05), from 1:1 to 21:1.

      0.03928 is the figure WCAG prints; the sRGB standard it borrows from says 0.04045. No 8-bit channel falls between the two — 10/255 is 0.0392 and 11/255 is 0.0431 — so the answer is the same either way.

      A colour typed as rgb(), hsl() or hwb() becomes 8-bit channels the way Chrome paints it, in its own 32-bit arithmetic. That matters on an exact half: hsl(35 50% 40%) has a green of 110.5 on paper, and the 111 measured is the 111 on the screen. Another browser may land such a half one step the other way.

      The ratio is shown to two decimals, cut, not rounded: #777777 on white is 4.4781, and it is shown as 4.47, never as a 4.48 that looks closer than it is, and never as a 4.50 that fails. Pass and fail are decided on the exact figure.

      The lines

      AA (success criterion 1.4.3, Contrast (Minimum)): 4.5:1 for text, 3:1 for large text. AAA (1.4.6, Contrast (Enhanced)): 7:1 and 4.5:1. Non-text (1.4.11, AA): 3:1 for the parts of a control and the graphics a person needs to see. Large text is at least 18 point (24 CSS pixels), or 14 point bold. WCAG leaves out logotypes, incidental text, decoration and inactive controls, and so does every verdict here.

      See-through colours

      A translucent colour has no contrast of its own; it has the contrast of what it becomes. So a see-through text colour is laid on its ground, and a see-through ground on what shows through it (the page of its edition unless you choose otherwise), both the way a browser paints — source-over, in sRGB — and rounded to the 8-bit colour a screen is actually sent. That colour is printed beside the result, and it is the one measured. The opacity is kept in 8 bits, as a browser keeps it, so .77 and the hex alpha C4 are one colour here as they are there. A browser’s own blending works in 8-bit steps of its own and can land one step either side on a channel, which moves a ratio by a few hundredths at most.

      The nearest passing shade

      It keeps the colour’s hue, saturation and opacity and moves only its HSL lightness: every step of a tenth of a percent from black to white, each rounded to the colour it would really be and measured whole. The passing step nearest where the colour started is offered, for the text and for the ground separately. At fixed hue and saturation every channel rises with lightness, so the walk passes through the colour’s own shades and never through another hue. When no lightness gets there — a grey too near the middle, a colour at 20 percent opacity — it says so, with what black and white would give.

      What it does not measure

      It is WCAG 2.x’s ratio, not APCA, the lightness-contrast method in the WCAG 3 drafts, which weighs dark-on-light differently from light-on-dark and reads font weight and size into the score. It does not see weight, typeface, anti-aliasing, a display’s own gamut, or colour-vision differences, and it reads sRGB only: lab(), oklch() and color() can name colours a hex cannot, and are refused in words rather than squeezed into range. A pass is a floor, not a promise that something reads well.

      Sources

      The definitions are the W3C’s: WCAG 2.2, contrast ratio and relative luminance, and the Understanding notes for 1.4.3, 1.4.6 and 1.4.11 (W3C Recommendation, W3C Document License). The colour syntax and the 148 names are CSS Color Module Level 4’s. The house colours are read from this site’s own stylesheets: assets/css/site.css for the paper edition and the deep band, and the dark edition’s sheet for its page, tint and inks.

      What happens to what you type

      Nothing is fetched and nothing is sent: every figure is worked out by the script in this page, on your device. The two pairs are written into the address bar after the #, so a copied link carries them; a browser never sends that part of an address to a server with the page request. The site’s analytics, on labs.llc only, may record the address as it stands, and a pair of colours is all that is in it. Nothing is stored beyond the edition and card switches every page keeps. Questions and corrections: labs@labs.llc.