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.

The dataset’s files
FileContentsSizeSHA-256
contrast-pairs.csvEvery ordered pair: text, background and the measurements.1.6 MBb62bd82983c3…
colors.csvThe 148 named colors, with luminance and OKLCH.8.8 kB103f337060a0…
datapackage.jsonA Frictionless Data Package: the files, their schemas, the license and the sources.6.1 kBd99c14207ebc…
reproduce.mjsRebuilds contrast-pairs.csv from colors.csv, byte for byte, with Node.js alone.4.7 kB78a6b08955eb…
matrix.pngThe figure below, as an image.5.1 kBbecdb9eb553a…
SHA256SUMS.txtThe checksums, in the format sha256sum -c reads.0.4 kBb72adb1d7d8b…

Check every file at once

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.

contrast-pairs.csv · Examples are from the row with gray text on white.
ColumnTypeMeaningExample
text_namestringThe text (foreground) color’s name.gray
text_hexstringThe text color’s sRGB value.#808080
background_namestringThe background color’s name.white
background_hexstringThe background color’s sRGB value.#FFFFFF
wcag_rationumberWCAG 2 contrast ratio, 1 to 21, to four decimals. The same in both directions.3.9494
wcag_aabooleanRatio at least 4.5: WCAG 2 AA for body text (1.4.3), and AAA for large text (1.4.6).false
wcag_aa_largebooleanRatio at least 3: AA for large text (1.4.3), and the 3:1 for interface parts and graphics (1.4.11).true
wcag_aaabooleanRatio at least 7: WCAG 2 AAA for body text (1.4.6).false
apca_lcnumberAPCA 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_2000numberCIEDE2000 color difference in CIELAB (D65), to two decimals. The same in both directions.33.24
colors.csv · Examples are from the row for rebeccapurple.
ColumnTypeMeaningExample
namestringThe CSS named color, lowercase, as CSS Color Module Level 4 lists it.rebeccapurple
hexstringIts sRGB value as #RRGGBB.#663399
rintegerRed, 0 to 255.102
gintegerGreen, 0 to 255.51
bintegerBlue, 0 to 255.153
relative_luminancenumberWCAG 2 relative luminance, 0 (black) to 1 (white), to six decimals.0.074923
oklch_lnumberOKLCH lightness, 0 to 1.0.4403
oklch_cnumberOKLCH chroma.0.1603
oklch_hnumberOKLCH hue in degrees; empty for grays, which have none.303.37
same_asstringOther 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.

A 148 by 148 grid of WCAG 2 verdicts. Rows are text colors and columns are backgrounds, both from darkest to lightest, with each color shown along the top and left edges. Passing cells gather in the lower-left and upper-right corners, where one color is dark and the other light; a wide failing band runs along the diagonal, where the two are close in lightness.
Every pair, shaded by verdict. Rows: text, darkest at the top. Columns: background, darkest on the left.
  • AAA, 7:1 or more
  • AA, 4.5:1 to 7:1
  • Large text only, 3:1 to 4.5:1
  • Below 3:1
How many pairs reach each WCAG 2 level
LevelPairsShare of pairs
AAA, 7:1 or more1,5146.9%
AA, 4.5:1 to 7:11,9709.0%
Large text only, 3:1 to 4.5:13,32215.2%
Below 3:115,09868.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.

Python 3

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")

JavaScript (Node.js)

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.

Terminal

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.

Plain text

Auric Artisan. (2026). CSS named color contrast pairs (Version 1.0.0) [Data set]. https://auricartisan.com/datasets/contrast-pairs/1.0.0/

BibTeX

@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. 1.0.0First release: 21,904 pairs of 148 named colors.

See also

Where this data is used

References

Sources and specifications

Found a mistake? Write to us and we will correct it.