2026-09-27 · 7 min · By Alcott Dube
How to pick accessible colour pairs that keep your brand
I choose accessible colour pairs by separating brand colours from functional roles, checking contrast ratios, and testing the combinations that actually appear in the interface.

I keep the brand recognisable by assigning its colours to suitable roles, then checking each foreground and background pair against the applicable accessibility threshold. When a pair fails, I change its shade, placement or treatment, then test the rendered component across states and themes.
What contrast ratio does interface text need?
I start with the minimum text requirements in the Web Content Accessibility Guidelines 2.2: 4.5:1 for ordinary text and 3:1 for large text. Large means at least 24 pixels in regular weight, or approximately 18.7 pixels in bold. A 20-pixel regular heading still needs 4.5:1. Calling something a heading doesn't change its threshold.
Those ratios apply to the foreground against the background behind it. I check labels, placeholders, helper text and button text, not just paragraphs. Pale placeholder text is a common failure, and it isn't a replacement for a persistent label. For enhanced text contrast, the thresholds rise to 7:1 for ordinary text and 4.5:1 for large text.
I record the required threshold beside each role before choosing colours. That prevents a visually pleasing swatch from becoming a constraint later. Logotypes and genuinely inactive controls have exceptions, but I don't treat those exceptions as a general licence for faint content. An exempt disabled label can still be frustrating to read.
How to calculate colour contrast correctly
The contrast calculation compares relative luminance, not the apparent distance between two hues. For standard screen colours, the calculation converts red, green and blue channels into linear-light values, then combines them with different weights. Green contributes more than blue. I use a tested calculator rather than estimating brightness from a colour picker's lightness value.
The ratio is (lighter luminance + 0.05) / (darker luminance + 0.05). It runs from 1:1 for identical luminance to 21:1 for black against white. Hue differences alone don't guarantee readable text. A bright orange and a bright green can look distinct while producing a poor luminance ratio.
I don't round a failure into a pass. On white, #777777 produces roughly 4.48:1 and fails the ordinary-text minimum; #767676 produces roughly 4.54:1 and passes. That tiny difference explains why eyeballing is unreliable. I also leave some margin where practical. A barely passing value offers little flexibility when someone later changes the surface or adds transparency.

How to keep brand colours when contrast fails
I separate brand recognition from functional colour. A signature yellow might belong in illustrations, large surfaces and decorative accents, without becoming small text on white. For example, #ffcc00 against white is about 1.51:1. Put black text on that yellow and the ratio becomes about 13.9:1. The yellow hasn't changed. Its job has.
For a yellow-led brand, I'd test a yellow button with dark text before inventing a darker yellow for every action. If that button needs a visible boundary against white to be identifiable, I'd add a sufficiently contrasting border or adjust its treatment. Passing text contrast doesn't automatically make the whole component distinguishable.
When coloured text is necessary, I create a darker functional variant for light surfaces and assess a lighter variant for dark surfaces. I retain the original colour where it works. I don't darken the entire palette or desaturate everything. The useful trade-off is usually a narrower set of approved uses, not a less recognisable brand.
How to build accessible colour tokens
I keep raw palette values separate from semantic tokens. A palette token describes an available colour; a semantic token describes its job. Names such as text.primary, surface.default, action.background and action.label make those jobs explicit. Components consume semantic tokens, so a theme adjustment doesn't require hunting through individual colour assignments.
The unit I approve is a pair, not a swatch. I document text.primary on surface.default and action.label on action.background, with their measured ratios and intended uses. A colour that passes on one surface may fail on another. Marking a single colour as accessible creates false confidence and encourages combinations nobody has checked.
I keep the initial token set small enough to audit. Primary text, secondary text, links, surfaces, borders, actions and feedback usually expose the main decisions. Each extra surface adds more potential pairings. For dark mode, I remap roles deliberately rather than reversing the palette. Shadows, borders and emphasis often need different treatment when the surrounding luminance changes.
How to check buttons, icons and error states
Text isn't the only thing that needs contrast. The non-text contrast requirement generally calls for 3:1 against adjacent colours for visual information needed to identify controls and their states, and for meaningful graphical objects. I check input boundaries, essential icons and state indicators according to what actually makes them identifiable.
I don't give every border a 3:1 target automatically. A decorative divider isn't equivalent to the only visible edge of an empty input. Likewise, a labelled button may not depend on its outer boundary for identification. I make that distinction explicitly rather than assuming every painted shape has the same requirement.
I also avoid making colour the only carrier of meaning. An error gets a useful message and, where appropriate, an icon or other visible cue. Links in body copy usually get underlines. A selected option needs an indicator beyond a hue change. I check hover, focus, selected and error treatments separately, including combinations such as a focused field that also has an error.
How to test contrast in real components
A token sheet is only the first check. Following the practical approach described by web.dev, I test colours where people encounter them: in rendered content. Opacity, gradients, background images and overlays can change the effective pair. For text over an image, I inspect the weakest area behind the letters, not an average sampled from the image.
I use automated checks to catch straightforward failures, then inspect cases the tools can't settle. A passing result doesn't prove that text over every crop of a photograph will remain readable. A solid text panel is often simpler to maintain than a carefully balanced overlay, especially when editors can replace the image.
I check actual type sizes, font weights and responsive layouts. Thin letterforms can remain uncomfortable even when their specified colours pass. I also use colour-vision simulations to expose meaning carried only by hue, without treating simulation as a substitute for user testing. Keyboard inspection catches focus indicators that looked convincing in an isolated design frame but disappear on particular surfaces.
How to prevent contrast regressions after handoff
I put approved pairings beside the component documentation, including theme and state. The handoff needs enough detail to answer a specific question: can this secondary label sit on this tinted card? If the answer requires someone to reconstruct the original design file, the documentation isn't doing its job.
For repeatable checks, I ask for contrast assertions on resolved token pairs and automated accessibility checks in component examples. Those checks cover different problems. Token tests catch a changed colour value; rendered checks can catch an unexpected background or style override. Neither replaces manual review of imagery, focus visibility or colour-only meaning.
When a change fails, I fix the narrowest responsible layer. One unusual banner may need its own surface treatment. Failures across several components may indicate a semantic token problem. I avoid local colour patches that quietly multiply exceptions. Before release, I want each exception either removed or documented with a reason, an owner and a clear limit on where it can be used.
Questions people ask
Can I use brand colours that fail contrast?
Yes, but I restrict their roles. A colour that fails as small text on white may work as a background with dark text or as decoration that carries no essential information.
Does every button need a 3:1 contrast ratio?
Not every button background must contrast 3:1 with its surroundings. I check whether its boundary is necessary to identify the control, then assess the applicable non-text requirement; its text still has a separate contrast requirement.
Do disabled buttons have to meet contrast requirements?
Genuinely inactive controls are exempt from the minimum text and non-text contrast requirements. I still keep them understandable and explain why an action is unavailable when that reason isn't obvious.
Do accessible light-mode colours work in dark mode?
Not automatically, because contrast depends on the pair rather than either colour alone. I test each dark-mode pairing and its component states instead of assuming an inverted palette will pass.