Pick accessible color contrast without guessing hex pairs

Contrast is measurable. If text looks “fine to me” on your calibrated monitor, it can still fail for users in sunlight or with low vision — and that is before dark mode swaps your palette.

What WCAG is actually asking

WCAG contrast ratios compare relative luminance of two colors. Body text generally needs 4.5:1 for AA; large bold text can use 3:1. UI components and focus rings have their own thresholds in the newer criteria — not everything is “gray text on white.”

Opacity tricks matter. `#666` at 60% opacity on a photo background is not the same as `#666` on flat white — the effective color is blended. We check the combination users actually see, not the hex in the design token file.

Brand colors can pass for headlines and fail for captions. Build a stepped neutral ramp for paragraphs and reserve saturated brand hues for accents that are allowed to be large or short.

Workflow that beats eyeballing

Define background tokens first (page, card, elevated surface), then assign foreground tokens per level. When you add dark mode, re-validate — inverting hex values rarely preserves ratio.

The Color Picker on DroidXP converts between HEX, RGB, and HSL in the browser so you can paste values from Figma, tweak luminance in HSL, and copy back without a desktop app. Use it in the same tab where you are editing CSS variables.

For components with multiple states (default, hover, disabled), check worst case: disabled text on tinted backgrounds is where teams usually slip under 4.5:1.

Beyond black and white

Charts and data viz need contrast between series, not just against the page. Do not rely on hue alone — add patterns or labels for color-blind readers.

Error reds and success greens should differ in luminance, not only hue, so status is obvious in grayscale screenshots too.

Placeholder text is not exempt from being readable, but it should still look secondary — bump size or weight instead of washing out to illegible gray.

Shipping contrast as a habit

Add contrast checks to your component library stories: one story per theme with automated axe or Lighthouse in CI. Catching failure in PR beats a late audit.

Document minimum pairs in your design system (“`--text-secondary` only on `--surface-raised`”). Developers stop improvising `#888` on arbitrary backgrounds.

Accessible color is not anti-brand — it is how more people actually read your app store listing, docs, and checkout flow.