Web features:
prefers-color-scheme media query
color-scheme
prefers-contrast media query
Forced colors
System colors
color-mix()
Oklab and OkLCh
Lab and LCH
Relative colors
Accessible color
Don't use color alone to indicate the presence of a user interface component or its state: Use iconography and/or shape to help differentiate. Do not denote success/errors ONLY with color. Use icons or text.
<!-- Good: Denotes state without colors alone -->
<div class="error-msg">
<span aria-hidden="true">❌</span>
<span>The password entered was invalid.</span>
</div>
Contrast
- Minimum contrast standards: Maintain 4.5:1 for normal text and 3:1 for large text or icons.
- Ensure non-text contrast standards: Maintain a minimum contrast ratio of 3:1 for user interface component boundaries and states.
- This includes visual elements (borders, backgrounds, box-shadows, underlines) that form the boundary or indicate the presence of a UI component (e.g., input field borders).
- This also includes visual elements indicating active states within a component (e.g., checkbox checkmarks or switch thumbs).
- Caveat: Meeting 3:1 non-text contrast can challenge minimalistic designs. Soft gradients or subtle inset/outset shadows can soften visual boundaries while satisfying accessibility requirements.
- Use
prefers-contrastonly when warranted: Reach for@media (prefers-contrast: more)when the design uses low-contrast accents (e.g., subtle borders, muted secondary text) that need to be reinforced; most sites that already meet baseline contrast won't need it.
Light and dark color schemes
- Support light and dark color schemes: Honor
@media (prefers-color-scheme: dark)and pair it with thecolor-schemeCSS property so form controls, scrollbars, and other UA-rendered surfaces match. See Dark mode for more details.
Forced Colors Mode
- Support Forced Colors Mode: In Forced Colors Mode (High Contrast on Windows), the browser overrides author colors with system keywords and strips
background-image,box-shadow, andborder-image. Define system color fallbacks for color tokens using@media (forced-colors: active). - Use
forced-color-adjust: noneonly where color is essential information (syntax highlighter, color picker swatch). DON'T useforced-color-adjust: nonejust to preserve aesthetics. - Don't rely on
background-image,box-shadow, orborder-imageto convey borders, separators, or state: They disappear in forced colors (and often in print too). If you must, ensure there's an alternative in forced colors mode, such asoutlineorborderwith system color keywords (CanvasText,LinkText,ButtonText,Highlight,GrayText, etc.).
Generating tints
Before generating tints dynamically, check if you can use an existing, predefined, design token. This allows much more designer control and ensures consistency.
If you need to generate lighter or darker colors dynamically:
- DO NOT just adjust the lightness channel in
oklch/oklaborlch/lab, e.g.oklab(from var(--primary) 0.9 a b). While that is theoretically the correct way, browsers do not yet implement gamut mapping, so the resulting color is unpredictable. - You can use
color-mix()to mix with white or black (preferably inoklab). This keeps the color safely in gamut, but tends to over-desaturate colors and produce washed out tints and shades. - You MAY combine lightness adjustment with any of the other methods (e.g.
color-mix(in oklab, oklch(from var(--primary) 0.9 c h), white 30%)) for a balance between the two, but avoid going above 30% for the lightness adjustment.