Guides
Color contrast for accessible UI
How to check contrast ratios, move between HEX and RGB, and build palettes that still read when someone cannot see your “subtle” gray.
Nico Vale · Jun 9, 2026 · 8 min read
A palette can look refined in a mock and fail in a product. The usual failure is not a loud accent. It is body text in a gray that seemed elegant on a calibrated laptop, sitting on a white that is slightly not white, in a room with sun on the screen.
Contrast is the most mechanical part of visual accessibility and the one you can measure. This guide covers ratios, HEX versus RGB, and how to check combinations before they harden into a design token.
What a contrast ratio is actually measuring
Contrast ratio compares the relative luminance of two colors. The number is a relationship, not a vibe. WCAG 2.x sets thresholds that are still the working language of audits and design systems:
- 4.5:1 for normal text (roughly under 18pt / 14pt bold, depending on the exact criteria you follow)
- 3:1 for large text
- 3:1 for non-text UI pieces that have to be distinguishable (icons, input borders) under the non-text contrast criterion
WCAG 3 is evolving. Shipping teams still get asked for 2.2 numbers. Meet 4.5:1 for paragraphs and you will fail fewer reviews than a mood board that “felt airy.”
Important caveats:
- The ratio does not know about font weight, tracking, or anti-aliasing. Thin gray type can fail in practice while passing on paper.
- Placeholder text, disabled controls, and charts have their own messy exceptions. Do not use “disabled is allowed to be faint” as a license for primary copy.
- Brand gradients behind text are a trap. Measure the worst local pairing, not the average.
Start with the pairs that users actually read
Audit in this order:
- Body text on the default page background
- Body text on cards, tables, and code blocks
- Link color on those same backgrounds (and visited, if you style it)
- Button labels on primary, secondary, and destructive fills
- Error and success text on alert backgrounds
- Form labels and helper text
- Chart labels and axis ticks
Footer muted copy is where teams hide sins. If legal text is unreadable, it is still text.
The contrast checker is for those pairs. Paste two colors, read the ratio, and keep a note of which token pair you tested. A single “brand blue vs white” check does not cover blue text on a blue-tinted surface.
HEX, RGB, and why you keep converting
Design files speak HEX (#1B2430). Browsers compute with sRGB channels. Developers paste rgb(27, 36, 48) into CSS or a canvas fill. None of this changes luminance if the color is the same; it does cause mistakes when someone rounds a channel or drops the #.
Use a HEX to RGB converter when you are matching a token from a screenshot, a PDF brand page, or a legacy stylesheet. Confirm you are in sRGB, not a Display P3 value that only looks like the same HEX in one app.
A color picker helps when the source is a live page: sample the computed background, not the layer you think is showing. Overlays, opacity, and mix-blend-mode mean the pixel is not the token.
When you pick from a photo or a logo, remember that sampled pixels include compression and shadows. Do not lock a 4.4:1 gray because the eyedropper caught a darker edge.
Palettes that survive real backgrounds
A practical system needs more than “primary” and “gray 500”:
- Text on light and text on dark as separate ramps. The same step number is rarely equally readable in both themes.
- Interactive colors that still hit 4.5:1 for labels, not only 3:1 for the fill versus the page.
- Muted text that stays at or above 4.5:1 if it is required reading (prices, dates, legal).
- Focus rings that contrast with both the control and the background.
If a color only works on white, it is not a text color. It is a decoration color. Put it on icons, charts, or large fills, and pair it with a darker sibling for type.
Test dark mode as a first-class surface. Inverting a light palette often produces dim text on dark gray that looks “cinematic” and fails immediately.
A workflow that fits in a pull request
- List the new or changed token pairs in the PR.
- Check each pair in the contrast tool. Paste HEX from the source of truth, not from memory.
- If a pair fails, change the text color first. Designers will thank you less for thickening a font than for keeping the type scale.
- Recheck hover, active, and disabled if those states use different fills.
- Spot-check on a phone in daylight, not only in the design file’s 100% zoom.
Automated axe runs catch some contrast issues. They miss text on images, text in canvas, and text in charts. Manual pairs still matter.
Contrast is not the whole of color accessibility
Do not encode meaning in hue alone. Error states need an icon or text, not only a red border. Charts need patterns or labels, not only a legend of similar blues. Contrast gets the words readable. Structure gets the meaning across.
If you take one habit from this guide: stop arguing about whether a gray “feels premium.” Measure the pair, fix the token, and move on. Readable UI is allowed to look calm. It is not allowed to disappear.