DOFIW ACCESSIBILITY GUIDE
How to Test an Accessible Color Palette for Websites and Apps
A role-based workflow for turning brand colors into dependable text, surface, border, focus, and status combinations—without treating every possible pair as equally useful.
Reviewed against WCAG 2.2 guidance · Practical guidance for design systems and content websites
An accessible palette is a set of approved relationships
A hexadecimal value has no contrast ratio by itself. Contrast exists only between a foreground and an adjacent background. The same green can be excellent for white button text, too faint as small green text on a pale surface, and completely unsuitable as a subtle border on white.
This changes the testing question. Instead of asking “Is our blue accessible?”, ask “May our primary blue be used for normal text on the default surface?” or “Does the input border remain identifiable against the page background?” Specific questions produce reusable design rules.
Step 1: inventory the colors that are actually rendered
Begin with the delivered interface, not only the brand PDF or design-file swatches. Record solid colors, opacity, overlays, gradients, borders, shadows used as meaningful boundaries, visited links, status colors, chart series, and theme variants.
If a color uses alpha transparency, calculate or inspect the composited color over every expected background. A token such as “black at 45%” produces different final colors on white, gray, photography, and a dark theme.
Browser styles can differ from design specifications because of inheritance, opacity, filters, blending, and component overrides. Test the final adjacent colors whenever possible.
Step 2: assign semantic roles before testing pairs
Role names explain where a color is allowed to appear. They also prevent a passing decorative shade from being reused as small text simply because it belongs to the same palette.
Prefer names such as text-primary and surface-error over names such as gray-700 and red-100 in component APIs. A scale name describes appearance; a semantic token describes intended use.
Step 3: build a useful contrast matrix
A full seven-color palette produces 21 unique pairs. A 20-color system produces 190. Most of those pairs will never appear together. Start with role combinations used by real components, then use a full matrix to discover safe alternatives and accidental combinations.
| Foreground role | Background role | Ratio | Approved use |
|---|---|---|---|
| Ink #172033 | White #FFFFFF | 16.27:1 | Normal text, large text, icons |
| Primary #175F54 | White #FFFFFF | 7.50:1 | Normal text and AAA target |
| Muted #5D6573 | White #FFFFFF | 5.88:1 | Normal secondary text at AA |
| Primary #175F54 | Soft #DFF1EB | 6.40:1 | Normal text at AA |
| White #FFFFFF | Soft #DFF1EB | 1.17:1 | Do not use for meaningful text or icons |
| Error #A9491B | Error soft #FBE7E2 | 4.82:1 | Normal error text at AA |
Use the DOFIW Color Palette Accessibility Checker to generate pairwise results, then annotate the combinations your components actually need. Verify critical individual pairs with the Color Contrast Checker.
Step 4: apply the threshold for the role
For WCAG Level AA, normal text needs 4.5:1 and qualifying large text needs 3:1. Meaningful visual information required to identify many interface components, states, and graphical objects generally needs 3:1 against adjacent colors under Non-text Contrast.
4.5:1 text pair
Body copy, labels, navigation, helper text, placeholder text, and other normal-size text.
3:1 large-text pair
Only text that meets the large-scale size and weight definition—not every heading or bold label.
3:1 component cue
A border required to find an input, a selected indicator, or an icon required to understand a control.
3:1 meaningful graphic
Lines, shapes, or regions needed to understand a chart or diagram, measured against adjacent colors.
For a full explanation of the text thresholds, large-scale definition, and exceptions, read WCAG Color Contrast Explained.
Step 5: test component states, not just static tokens
A button can pass in its default state and fail after hover, selection, validation, or focus. Create a state inventory for every interactive component and check both its text and the visual information needed to identify the component or state.
- Confirm that hover styling does not make text or the component disappear against adjacent colors.
- Check focus indicators against the colors they touch. An outside outline is normally compared with the outside background.
- Check the selected mark inside checkboxes, radios, toggles, and menu items against its adjacent fill.
- Review error text, field borders, icons, and inline help as separate visual relationships.
- Treat inactive components according to the relevant exception, while still designing a state users can understand.
Step 6: remove color-only meaning
A palette can pass every luminance test and still fail users if color is the only way to identify information, an action, or a state. WCAG 1.4.1 requires another visual means when color carries meaning.
Add text, icons, patterns, shapes, underlines, or other visible distinctions. For data visualizations, label series directly when practical and combine hue with patterns or marker shapes. Do not assume a color-vision simulation proves that all meaning remains available.
A green and red status may have strong luminance contrast and still require labels such as “Approved” and “Rejected” because users must identify the specific color meaning.
Step 7: repeat the test in every visual context
Approved pairs are context-specific. A light-theme text token cannot automatically be used on a dark surface, and an overlay that works on one photograph may fail on another.
Dark theme
Retest text, muted content, dividers, controls, focus rings, and status surfaces. Do not merely invert light-theme values.
Photography and gradients
Test the least favorable region text can occupy, including responsive crops and animated backgrounds.
Transparency
Calculate the final composited color for each allowed surface rather than testing the translucent token as if it were opaque.
User settings
Review forced-colors behavior, user styles, zoom, and theme controls so essential distinctions survive adaptation.
A repeatable palette-testing workflow
- Collect rendered colors.Include opacity, surfaces, states, themes, and graphics—not only brand swatches.
- Assign semantic roles.Describe what each token may do: text, surface, boundary, state, focus, or data.
- List real adjacency pairs.Map components before generating every mathematical combination.
- Calculate the full matrix.Use it to find approved alternatives and combinations that should be prohibited.
- Apply role-specific thresholds.Separate normal text, large text, component cues, and meaningful graphics.
- Review color-only meaning.Add visible labels, shapes, icons, patterns, or underlines where meaning depends on hue.
- Test states and contexts.Cover focus, hover, error, selection, disabled, dark mode, images, and responsive layouts.
- Document and enforce.Publish approved token pairs with examples and component rules for designers and developers.
Document combinations as contracts
A contrast spreadsheet is useful during an audit, but a design system needs rules that prevent regressions. For each semantic token, document allowed backgrounds, approved content sizes, component states, and prohibited combinations.
Good token guidance
text-muted may be used on surface-default and surface-soft for normal text. Do not use it on brand surfaces.
Weak token guidance
“Gray 600 passes AA.” This omits the background, text size, component role, opacity, and state.
Add automated tests for token pairs where your codebase permits it, but keep visual and human review in the release process. Automation cannot decide whether information relies on color alone or whether a graphic part is required for understanding.
Common palette-testing mistakes
- Calling an individual color accessible without naming its adjacent color and role.
- Testing every mathematical pair but failing to document which combinations are permitted.
- Checking text contrast while ignoring boundaries, icons, charts, and state indicators.
- Using the large-text threshold for headings that do not meet the size and weight definition.
- Testing opaque hexadecimal values when the interface uses opacity or blending.
- Checking the default state only and missing hover, focus, selected, error, and disabled styles.
- Assuming red and green are distinguishable because both contrast with white.
- Reusing a light-theme palette in dark mode without rebuilding adjacency rules.
- Rounding a value below 3:1 or 4.5:1 up to the threshold.
- Treating a successful automated matrix as proof of complete accessibility.
Accessible palette checklist
- Every token has a semantic role, not only a visual scale name.
- Real foreground and adjacent-background combinations are listed.
- Normal text, large text, non-text cues, and graphics use the correct thresholds.
- Opacity, overlays, gradients, and image regions use their final rendered colors.
- Default, hover, focus, selected, error, and disabled states are reviewed.
- Light, dark, and user-adjustable themes have separate approved pairs.
- Status and chart meaning remains available without identifying a hue.
- Approved and prohibited combinations are documented with examples.
- Critical token pairs have regression tests where practical.
- Human review confirms that the interface remains understandable in context.
Use the DOFIW color workflow
Frequently asked questions
How many colors can an accessible palette contain?
There is no fixed limit. Complexity grows quickly, so give every color a role and restrict combinations instead of allowing every token everywhere.
Does every pair in a palette need to pass 4.5:1?
No. Only pairs used for normal text need that threshold. Other roles may use different requirements, while purely decorative combinations may have no contrast requirement.
Can a brand color remain unchanged?
Yes. Keep it for contexts where it works, pair it with a different surface, or create an accessible role-specific shade for text and controls. Brand identity does not require every token to perform every job.
Is a contrast matrix enough to test charts?
No. It helps compare adjacent colors, but you must also decide which graphic parts are necessary and ensure information is not conveyed by hue alone.
Should disabled controls use low contrast?
Inactive components have relevant WCAG contrast exceptions, but users still benefit from readable labels and a clear explanation of how to enable the control.
How often should the palette be retested?
Retest when tokens, components, typography, themes, overlays, or brand colors change. Automated regression checks can catch known pair failures between larger reviews.
Official sources
This guide is educational information, not legal advice, certification, or a guarantee of WCAG conformance. Test the complete product with appropriate automated and human methods.
