A colour code is one screen colour written as text, and CSS accepts the same colour in several notations: #FF5733 in hex or hsl(11, 100%, 60%). Rounded to whole degrees and percentages, the two stop being interchangeable. On 4 November 2026 we sent all 16,777,216 colours that six-digit hex can name through whole-number hsl() and back. Only 1,775,554 returned unchanged, about one in nine. The rest moved by up to five steps on a red, green or blue channel.
Whole-number hsl() brings back about 11% of hex colours unchanged
This isn't a new problem. Mozilla's bug 372783, opened in 2007, carries a 2009 comment noting that integer HSL "only allows 3.6 million colors" against 16.8 million RGB ones. A 2017 Stack Overflow answer counted 3,518,202 distinct integer HSL triples and advised keeping floating-point values internally.
Our sweep quantifies the problem precisely. The 3,672,360 whole-number inputs land on only 2,037,899 distinct colours, 12.15% of the cube, because greys collapse and many triples round to the same byte. We ran every sweep through the conversion code behind our colour code converter.
| Route | Returned exactly, of 16,777,216 | Largest channel error |
|---|---|---|
| hex → whole-number hsl() → hex | 1,775,554 (10.58%) | 5 |
| hex → whole-number HSB → hex | 2,044,773 (12.19%) | 3 |
| hex → whole-percent naive CMYK → hex | 2,136,545 (12.73%) | 2 |
Type #FF5733 into the converter's HEX field and the HSL(A) field reads hsl(11, 100%, 60%). Type that same hsl() text into the HSL(A) field and the HEX field reads #FF5833, one step higher on green. CSS accepts decimals in hsl(), so the loss belongs to the whole-number habit rather than to HSL itself.
Profile-free cmyk() differs visibly from both press profiles in the W3C's examples
CSS Color Module Level 5, a W3C Working Draft dated 29 October 2026, prints a naive formula, the one our converter also applies: black is one minus the largest of red, green and blue, and each ink takes what remains. Its own example turns one value, device-cmyk(0.091777 0.043303 0.312816 0), into three colours. The SWOP 2006 coated and FOGRA39 press profiles differ by a colour difference (ΔE2000) of 8.17, "clearly visible"; the naive result against SWOP gives 14.3, "very visible". On the draft's scale, 1 or more "is just visible, while 5 or more is just a different color altogether".
Our ΔE2000 code reproduced the 8.17, gave 14.25 for the 14.3, then compared naive results with three more profile values the draft prints.
| CMYK (%) | Naive sRGB | Profile in the draft | ΔE2000, our arithmetic |
|---|---|---|---|
| 0 / 81 / 81 / 30 | rgb(179, 34, 34) | FOGRA39 | 5.85 |
| 0 / 70 / 20 / 0 | rgb(255, 77, 204) | SWOP 2006 coated | 14.27 |
| 90 / 0 / 90 / 0 | rgb(25, 255, 25) | FOGRA39, outside sRGB | 27.38 |
Profiles do not always disagree badly: the draft gives 0/70/20/0 under both, and those two values sit 1.11 apart on our arithmetic.
Naive cmyk() never mixes cyan, magenta and yellow together
The formula always moves the grey part of a colour into black. Across all 16,777,216 colours, no naive result uses cyan, magenta and yellow at once. The draft explains why these numbers describe no particular press: the naive conversion "has no knowledge of the colorimetry of the inks, the dot gain, the colorimetry of the RGB space, and so on." The draft's remedy is a colour profile, though it marks @color-profile and device-cmyk() as at risk. Our ΔE figures above describe the draft's examples only, never any printer.
Each #rrggbb colour reaches AA on white or black, and 38.6% miss AAA on both
WCAG 2.2 defines contrast as (L1 + 0.05) / (L2 + 0.05), with L the relative luminance, and its Success Criteria 1.4.3 and 1.4.6 ask for 4.5:1 for text below large size at AA and 7:1 at AAA. Multiply a colour's ratio against white by its ratio against black and the luminance cancels, leaving 21. The better of the two is therefore never below √21, about 4.58, for any opaque colour. Mark Ransom derived the crossover, a luminance near 0.179, in a Stack Overflow answer of 2010.
On our sweep, 6,482,445 of the 16,777,216 colours (38.64%) reach 7:1 against neither white nor black: those whose relative luminance lies strictly between 0.1 and 0.3. By the same product, that set is exactly the one clearing 3:1 against both. CSS Color 5 adds that "sufficient color contrast is only one piece of the puzzle". Colour vision is a separate question; see our guide to colour-blindness simulation models.
A ratio printed to two decimals can show a pass the verdict refuses
W3C's Understanding document for SC 1.4.3, informative guidance rather than part of the standard, says "the computed values should not be rounded (e.g., 4.499:1 would not meet the 4.5:1 threshold)". A checker that grades the unrounded value but prints two decimals will still display a threshold it has refused. Of 33,554,432 pairs, every 8-bit colour against white and against black, 17,917 print as 4.50 while falling below 4.5, and 22,806 print as 3.00 below 3. #006BFB on black is one: its ratio is 4.49608.
The Understanding document says nothing about transparency, so we chose a method: blend the colour onto the background, round to the byte an 8-bit screen shows, then grade that. #FF5733 at 50% alpha over white becomes #FFAB99, 1.82:1, a fail, against 3.15:1 if graded as opaque. Before a revision dated 4 November 2026, our converter printed two decimals and graded a translucent colour as opaque; the revised version prints 4.496 for #006BFB on black and grades the blended colour.
display-p3 and oklch() reach far beyond what an sRGB screen can show
CSS Color Module Level 4, a Candidate Recommendation Draft of 3 November 2026, defines lab(), oklch() and display-p3 alongside hex and hsl(), and says a valid colour outside what a device can produce "is said to be out of gamut". With the draft's own matrices, we found just under half of the 8-bit display-p3 cube outside sRGB, allowing a small rounding tolerance. We also stepped oklch() through lightness in 1% steps, chroma from 0 to 0.40 in steps of 0.01 and every whole-degree hue, 1,490,760 values in all. With the same tolerance, 423,852 (28.43%) land inside sRGB; with none, 27.77%. CSS Color 5's FOGRA39 green from the table above is outside sRGB yet, in that draft's words, "inside the display-p3 gamut".
Large text in WCAG is set in points, with its own note for CJK type
The lower thresholds, 3:1 at AA and 4.5:1 at AAA, apply to large-scale text, which WCAG 2.2 defines as "at least 18 point or 14 point bold or font size that would yield equivalent size for Chinese, Japanese and Korean (CJK) fonts". It gives no separate size for CJK type. W3C's Understanding document converts the Latin sizes at 1pt = 1.333px, "approximately 18.5px and 24px".
In Singapore, the Ministry of Digital Development and Information's reply to a parliamentary question, dated 12 November 2024, "MDDI's response to PQ on Ensuring High Traffic Government Websites Conform To WCAG", states that the Government "plans to make all high-traffic Government websites accessible by 2030". Its scope is high-traffic Government websites alone.