Dataset · Open data
Contrast pairs Every CSS named color, as text on every other
All 148 named colors of CSS, each measured as text on each of them as a background: 21,904 ordered pairs, with the WCAG 2 contrast ratio and its AA and AAA verdicts, the APCA lightness contrast and the CIEDE2000 color difference. It answers which named colors can be used together, and how often WCAG 2 and APCA disagree.

- Version
- 1.0.0
- Rows
- 21,904
- License
- CC BY 4.0
- Published
Download
The files
Each file sits under its version, 1.0.0, so a link or a citation to it always returns these exact bytes. Check a download against its SHA-256.
| File | Contents | Size | SHA-256 |
|---|---|---|---|
| contrast-pairs.csv | Every ordered pair: text, background and the measurements. | 1.6 MB | b62bd82983c3… |
| colors.csv | The 148 named colors, with luminance and OKLCH. | 8.8 kB | 103f337060a0… |
| datapackage.json | A Frictionless Data Package: the files, their schemas, the license and the sources. | 6.1 kB | d99c14207ebc… |
| reproduce.mjs | Rebuilds contrast-pairs.csv from colors.csv, byte for byte, with Node.js alone. | 4.7 kB | 78a6b08955eb… |
| matrix.png | The figure below, as an image. | 5.1 kB | becdb9eb553a… |
| SHA256SUMS.txt | The checksums, in the format sha256sum -c reads. | 0.4 kB | b72adb1d7d8b… |
sha256sum -c SHA256SUMS.txt
Schema
What each column means
Comma-separated, UTF-8, a header row, LF line ends and no quoting: no value contains a comma. Booleans are true and false. The example is a real row.
| Column | Type | Meaning | Example |
|---|---|---|---|
text_name | string | The text (foreground) color’s name. | gray |
text_hex | string | The text color’s sRGB value. | #808080 |
background_name | string | The background color’s name. | white |
background_hex | string | The background color’s sRGB value. | #FFFFFF |
wcag_ratio | number | WCAG 2 contrast ratio, 1 to 21, to four decimals. The same in both directions. | 3.9494 |
wcag_aa | boolean | Ratio at least 4.5: WCAG 2 AA for body text (1.4.3), and AAA for large text (1.4.6). | false |
wcag_aa_large | boolean | Ratio at least 3: AA for large text (1.4.3), and the 3:1 for interface parts and graphics (1.4.11). | true |
wcag_aaa | boolean | Ratio at least 7: WCAG 2 AAA for body text (1.4.6). | false |
apca_lc | number | APCA lightness contrast of this text on this background, to two decimals. Positive for dark text on a light background, negative for light on dark. | 66.9 |
delta_e_2000 | number | CIEDE2000 color difference in CIELAB (D65), to two decimals. The same in both directions. | 33.24 |
| Column | Type | Meaning | Example |
|---|---|---|---|
name | string | The CSS named color, lowercase, as CSS Color Module Level 4 lists it. | rebeccapurple |
hex | string | Its sRGB value as #RRGGBB. | #663399 |
r | integer | Red, 0 to 255. | 102 |
g | integer | Green, 0 to 255. | 51 |
b | integer | Blue, 0 to 255. | 153 |
relative_luminance | number | WCAG 2 relative luminance, 0 (black) to 1 (white), to six decimals. | 0.074923 |
oklch_l | number | OKLCH lightness, 0 to 1. | 0.4403 |
oklch_c | number | OKLCH chroma. | 0.1603 |
oklch_h | number | OKLCH hue in degrees; empty for grays, which have none. | 303.37 |
same_as | string | Other names with exactly the same value, separated by spaces; empty if none. | — |
Method
How it was measured
- The colors. The 148 named colors of CSS Color Module Level 4, from aliceblue to yellowgreen. 139 distinct values: gray and grey, aqua and cyan, fuchsia and magenta are among the names that share one.
- WCAG 2. Relative luminance from the sRGB values, then (L1 + 0.05) / (L2 + 0.05) with the lighter color on top. The linearization switches at 0.04045; WCAG 2’s text says 0.03928, and for 8-bit values the two give identical results.
- Verdicts. Each verdict compares the unrounded ratio with 4.5, 3 and 7. Rounding the ratio to four decimals changes no verdict in this dataset.
- APCA. APCA-W3 0.0.98G-4g, measured with the first color as text. Here Lc reaches ±107.88; its sign gives the polarity, and pairs too close to measure are 0.
- CIEDE2000. The 2000 color-difference formula on CIELAB computed from sRGB with the D65 white, without chromatic adaptation.
- The code. The site’s color engine, the code its contrast tools run, wrote the files.
reproduce.mjs, published with them, carries the same formulas and rebuilds contrast-pairs.csv from colors.csv byte for byte, with Node.js alone.
Validation
How it was checked
Checked on against sources this site did not write. Each figure is the largest difference found across all 21,904 rows.
- Names and values. All 148 of the 148 named colors match the table in CSS Color Module Level 4 (9 October 2026).
- WCAG 2. Every luminance and every unrounded ratio is identical to culori 4.0.2; the published ratios differ by at most 0.00005, the rounding.
- APCA. Every Lc matches apca-w3 0.1.9, the reference implementation, within 0.005, the rounding.
- CIEDE2000. Every value matches the delta-e package 0.0.8 within 0.005, the rounding. On culori’s own CIELAB, whose D65 white differs in the fourth decimal, the values move by at most 0.015.
- Structure. 21,904 rows, one for each ordered pair of names; ratios and ΔE are the same both ways; a positive Lc always has the darker color as text, and a negative one the lighter.
- Rebuild.
reproduce.mjs, run on colors.csv, writes a contrast-pairs.csv with the published SHA-256. - The examples. The Python and JavaScript programs below were run on the CSV (Python 3.12.12, Node.js v24.11.0) and printed exactly the output shown.
What it shows
The whole table, at a glance
Each row of the figure is a text color and each column a background, both ordered from darkest to lightest. A cell is shaded by its WCAG 2 verdict.
- AAA, 7:1 or more
- AA, 4.5:1 to 7:1
- Large text only, 3:1 to 4.5:1
- Below 3:1
| Level | Pairs | Share of pairs |
|---|---|---|
| AAA, 7:1 or more | 1,514 | 6.9% |
| AA, 4.5:1 to 7:1 | 1,970 | 9.0% |
| Large text only, 3:1 to 4.5:1 | 3,322 | 15.2% |
| Below 3:1 | 15,098 | 68.9% |
- White takes 32 of the 148 named colors as body text at AA; black takes 116.
- 650 pairs pass WCAG 2 AA but measure below Lc 60 in APCA.
Reproduce
Run the analysis yourself
Download contrast-pairs.csv into a folder and run either program there. Both use only the standard library, and both print the output below.
import csv
with open("contrast-pairs.csv", newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
aa = [r for r in rows if r["wcag_aa"] == "true"]
print(len(rows), "pairs;", len(aa), "pass AA for body text")
# Which backgrounds take the most named colors as body text?
counts = {}
for r in aa:
counts[r["background_name"]] = counts.get(r["background_name"], 0) + 1
for name, n in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))[:3]:
print(name, n)
# Pairs WCAG 2 passes that APCA measures below Lc 60.
low = [r for r in aa if abs(float(r["apca_lc"])) < 60]
print(len(low), "pass AA but measure below Lc 60")
import { readFileSync } from "node:fs";
const [head, ...lines] = readFileSync("contrast-pairs.csv", "utf8").trim().split(/\r?\n/);
const keys = head.split(",");
const rows = lines.map((line) => Object.fromEntries(line.split(",").map((v, i) => [keys[i], v])));
const aa = rows.filter((r) => r.wcag_aa === "true");
console.log(rows.length, "pairs;", aa.length, "pass AA for body text");
// Which backgrounds take the most named colors as body text?
const counts = new Map();
for (const r of aa) counts.set(r.background_name, (counts.get(r.background_name) || 0) + 1);
[...counts]
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
.slice(0, 3)
.forEach(([name, n]) => console.log(name, n));
// Pairs WCAG 2 passes that APCA measures below Lc 60.
const low = aa.filter((r) => Math.abs(Number(r.apca_lc)) < 60);
console.log(low.length, "pass AA but measure below Lc 60");
Output
21904 pairs; 3484 pass AA for body text
black 116
navy 97
darkblue 96
650 pass AA but measure below Lc 60
Rebuild the CSV
In a folder with colors.csv, reproduce.mjs and SHA256SUMS.txt, this writes contrast-pairs.csv and checks it against the published checksum.
node reproduce.mjs
sha256sum --ignore-missing -c SHA256SUMS.txt
Citation
How to cite it, and what you may do
Cite the version you used. The license lets you share and adapt the data, for any purpose, as long as you give credit.
Auric Artisan. (2026). CSS named color contrast pairs (Version 1.0.0) [Data set]. https://auricartisan.com/datasets/contrast-pairs/1.0.0/
@misc{auricartisan_contrast_pairs_1_0_0,
author = {{Auric Artisan}},
title = {{CSS} named color contrast pairs},
year = {2026},
version = {1.0.0},
howpublished = {\url{https://auricartisan.com/datasets/contrast-pairs/1.0.0/}},
note = {Data set. Licensed {CC BY 4.0}}
}
License: Creative Commons Attribution 4.0 International. The color names and values come from CSS Color Module Level 4, a W3C specification.
Versions
What changed, and when
- 1.0.0First release: 21,904 pairs of 148 named colors.
See also
Where this data is used
References
Sources and specifications
- CSS Color Module Level 4: named colorswww.w3.org
- WCAG 2.2: contrast ratiowww.w3.org
- WCAG 2.2: relative luminancewww.w3.org
- APCA-W3: the reference implementation (apca-w3)github.com
- Sharma, Wu and Dalal: the CIEDE2000 color-difference formulahajim.rochester.edu
- Frictionless Data Packagedatapackage.org
- Creative Commons Attribution 4.0 International: legal codecreativecommons.org
Found a mistake? Write to us and we will correct it.