A palette you can inspect, ship and use again
Turn named opaque hex colours into collision-safe CSS custom properties and structured JSON, with WCAG relative luminance and the stronger of black or white text measured for every swatch.
CSS custom properties
One colour variable and one suggested text-colour variable per token, inside a :root block.
Structured palette JSON
Names, canonical hex and RGB values, relative luminance, suggested black or white text and its contrast ratio.
Every palette colour
| ink | #1f2937 | 31, 41, 55 | 0.02 | #ffffff | 14.68 |
| paper | #f8fafc | 248, 250, 252 | 0.95 | #000000 | 20.07 |
| accent | #ea980e | 234, 152, 14 | 0.4 | #000000 | 9 |
| success | #16803a | 22, 128, 58 | 0.16 | #ffffff | 5.02 |
Results are provided as-is, with no warranty of accuracy. The method and its sources are published below so you can check the working.
How it works
Relative luminance is not average brightness
For each channel, the two hex digits are converted to a number from 0 to 255, divided by 255 and linearised:
linear(c) = c / 12.92 when c <= 0.04045
linear(c) = ((c + 0.055) / 1.055) ^ 2.4 otherwise
L = 0.2126 R + 0.7152 G + 0.0722 B
Green contributes more to relative luminance than blue, and the linearisation
means two encoded channel values cannot be averaged before the calculation.
The contrast ratio for two luminances is (lighter + 0.05) / (darker + 0.05).
The tool calculates that ratio once with black at luminance 0 and once with white at luminance 1. Whichever is larger becomes the suggested text token.
The default palette
| Token | Hex | Best text | Contrast ratio |
|---|---|---|---|
| ink | #1f2937 | white | 14.6791 |
| paper | #f8fafc | black | 20.0712 |
| accent | #ea980e | black | 8.9961 |
| success | #16803a | white | 5.0205 |
The headline outputs are 4 colours, 2 using light text, 0 renamed
tokens, and a lowest best-text ratio of 5.02 after display rounding. That
minimum belongs to success; it is not an average across the palette.
What the files contain
The CSS download keeps the pairing close to the colour:
:root {
--brand-ink: #1f2937;
--brand-ink-text: #ffffff;
}
The JSON download carries the canonical hex, integer RGB channels, relative
luminance, suggested text and unrounded-to-display contrast ratio. Three-digit
hex such as #abc becomes #aabbcc in both files, so downstream consumers do
not have to support two spellings.
CSS custom-property names are case-sensitive. Normalising all entered names to
one ASCII kebab-case convention prevents --brand-Ink and --brand-ink from
looking like the same token while behaving as different properties.
What the result does not certify
WCAG’s normal-text target is a property of an actual foreground and background as rendered. A token file cannot know whether text will be large, bold, disabled, placed over a gradient, blended with opacity or changed on hover. It also cannot replace focus visibility or non-colour status cues.
Treat the paired variable as a strong default that has been measured, then test the real component. Use the PNG to JPEG converter only for file format work; changing an image container does not make its embedded text accessible.
How it is done
- Parse one name plus an opaque three- or six-digit hex colour per non-empty line, expanding shorthand to canonical lower-case six-digit RGB.
- Normalise names and the prefix to lower-case ASCII kebab-case, adding -2, -3 and later suffixes rather than overwriting duplicate tokens.
- Linearise each encoded sRGB channel, combine relative luminance with the WCAG weights and compare the resulting contrast against black and white.
- Export one colour and suggested-text custom property per token in CSS and the same values, channels, luminance and ratio as structured JSON.
What it assumes
- Every entered colour is opaque. Alpha colours are refused because their displayed contrast depends on an unstated surface underneath.
- The reported text choice compares only #000000 and #ffffff; it does not search every possible foreground colour.
- A higher measured ratio does not by itself make a complete component accessible; type size, weight, states, focus, non-text cues and actual rendered colours still matter.
- Hex values are interpreted as sRGB and no ICC profile or wide-gamut colour space is attached to the exported tokens.
Common questions
Is black or white always the better text colour?
One of them always has the higher ratio against an opaque colour, and the tool reports that measured winner. “Better” here means contrast only; brand constraints and the rest of the component still need review.
Why not average the red, green and blue numbers?
Hex channels are gamma-encoded sRGB values, not proportional light. WCAG first converts each channel to a linear value and then applies different red, green and blue weights.
What happens when two names become the same CSS token?
The first keeps the base name and later matches receive -2, -3 and so on. No declaration is silently replaced, and the table shows every final token before download.
Why are eight-digit hex colours rejected?
Their alpha channel blends with a background before contrast exists. A reliable ratio would need that background as another explicit input, so this tool accepts only opaque #RGB and #RRGGBB values.