How color intensity interacts with contrast, readability, and perceptual weight — and why the right chroma for a button depends on its hue, its role, and whether it's a fill, a text color, or a border.
Part III-A established the color budget, the hue map, and the hierarchy-semantic matrix. But every specimen in that document used colors at a single, "standard" saturation — the midpoint that felt natural for each hue. That was a simplification. In practice, saturation is a separate axis of design decisions with its own constraints, tradeoffs, and failure modes.
A fully saturated blue button and a muted blue button with the same lightness will produce the same WCAG contrast ratio with white text. Contrast, as measured by WCAG 2.x, is a pure function of relative luminance — it is entirely hue-blind and saturation-blind. And yet the two buttons do not read the same way. The saturated one feels louder, more urgent, more "primary." The muted one feels quieter, more restrained, more "background." This perceptual difference is real, meaningful, and not captured by any contrast formula.
This document maps the saturation dimension: from zero chroma (pure gray) through the muted and pastel ranges, through the "standard" range where most design systems operate, to the maximum chroma boundary where sRGB gamut clips. Along the way, it identifies the zones where buttons become too washed out to read as interactive, where colors vibrate against dark backgrounds, and where different hues have dramatically different available chroma ranges.
HSL saturation is a lie. It reports 100% for both a vivid blue and a vivid yellow, despite the fact that the blue appears dramatically more intense to the human eye. OKLCH chroma is the corrective: a perceptually uniform measure of colorfulness that accurately reflects what you actually see.
In HSL, saturation is relative to the current hue and lightness — it simply measures how far the color is from the equivalent gray. This makes it useless for cross-hue comparison. A blue at hsl(220, 80%, 50%) and a yellow at hsl(50, 80%, 50%) have the same "saturation" number but wildly different perceptual intensity. The yellow appears far more vivid because yellow inherently carries more luminance.
OKLCH chroma (the C axis) solves this by measuring colorfulness on an absolute perceptual scale. A chroma of 0.15 looks roughly equally vivid regardless of hue. This makes it the correct tool for constructing button palettes where different semantic colors need to feel equally intense.
Notice the difference. The HSL row has wild visual variation — the yellow and cyan feel like they're glowing while the blue and purple feel much heavier. The OKLCH row feels even. Each hue carries approximately the same visual weight. This is why OKLCH is the correct space for building a button color system: it lets you reason about chroma independently of hue, and trust that the numbers produce consistent visual outcomes.
Not all chroma levels work for button backgrounds. Too low and the button looks disabled or decorative. Too high and it overwhelms the interface. The viable range is narrower than you might expect, and it varies by hue.
Here is a single hue (blue, H:260) at a fixed lightness suitable for white text (L:0.50), shown across the full chroma range from zero to maximum sRGB gamut. The question for each level: does this read as an interactive, clickable button?
At chroma 0.00–0.03, there is no discernible color. The button reads as neutral gray. At 0.06, a faint blue tint is visible but the button feels "disabled" or "muted" — most users would not read it as a primary call-to-action. At 0.10–0.14, the color is clearly identifiable as blue and the button feels interactive. This is the lower threshold for a standard-weight button fill. At 0.18–0.22, the button feels confident and saturated — this is where most modern design systems land for their primary CTA. Above 0.22, the color becomes quite intense and can feel aggressive.
Now the same test for red (H:25):
The red strip behaves similarly but reaches its sRGB gamut limit sooner. This reveals the first key principle: every hue has a different maximum chroma at a given lightness. Blue can push to ~0.27 at L:0.50 within sRGB. Red maxes out around 0.23. Green maxes around 0.18. Yellow, because it requires much higher lightness to be recognizable, has entirely different limits.
| Chroma range | Name | Button viability |
|---|---|---|
0.00–0.03 |
Near-achromatic | No color identity. Reads as neutral/gray. Usable only for neutral-role buttons. |
0.03–0.07 |
Tinted gray | Faint color cast. Feels disabled or decorative. Not viable for primary fills. Useful for tinted ghost fills and disabled states. |
0.07–0.12 |
Muted / soft | Color is identifiable but quiet. Suitable for secondary and tertiary buttons. Feels understated — editorial, luxury, or subdued brands. |
0.12–0.20 |
Standard / vivid | The sweet spot for primary button fills. Color is unambiguous, button feels interactive and confident. Most design systems operate here. |
0.20–0.30 |
Intense / neon | Very saturated. Can feel aggressive or "marketing." Use sparingly — single CTA on a landing page, not in a dense application. |
0.30+ |
Out of sRGB gamut | Requires P3 displays. Will be clipped on standard monitors. Not safe for production button systems. |
This is the most counterintuitive fact in button color design: changing saturation while holding lightness constant does not change the WCAG contrast ratio. A gray button and a vivid blue button at the same OKLCH lightness produce the same ratio against white text.
WCAG 2.x contrast is computed from relative luminance, which is a weighted sum of linearized R, G, and B values. When you add chroma (color intensity) to a surface at a fixed perceptual lightness, the R, G, and B channels redistribute but their weighted luminance sum stays approximately the same. The contrast ratio barely moves.
The ratios barely change — from 5.6:1 to 5.3:1 across the full chroma range. All four buttons pass WCAG AA. From a compliance perspective, they are interchangeable. But they are obviously not perceptually interchangeable. The gray button and the vivid blue button feel completely different in weight, urgency, and interactivity.
This reveals the fundamental gap in WCAG's model as it applies to buttons: contrast ratio measures legibility, not communicative power. A muted, low-chroma button can pass contrast ratios while failing to signal "this is a primary action" because it lacks the visual assertiveness that chroma provides. Conversely, a highly saturated button can pass contrast while feeling so aggressive that users hesitate to click it.
The sRGB gamut is not a uniform shape. Some hues can be pushed much further in chroma than others at the same lightness. If you try to use the same chroma value across all your semantic colors, some will be vivid and others will be gamut-clipped into something unexpected.
This matrix shows six chroma levels across eight hue families, all at L:0.55 (a lightness suitable for white text on most hues). Cells that would fall outside the sRGB gamut are marked — the browser will clip them to the nearest representable color, producing unpredictable results.
Blue has the deepest gamut at this lightness — it can be pushed to C:0.25 before clipping. Yellow and cyan clip first, around C:0.13–0.15. This means you cannot use a single chroma value for all your semantic colors and expect them to look equally vivid. Instead, you need to pick a target perceptual intensity and find the maximum chroma each hue supports at that intensity, stopping short of the gamut boundary.
Text color and fill color have fundamentally different chroma requirements. A button fill occupies a large visual area — even low chroma is perceptible. Text is thin, rendered at small sizes, and competes with the surrounding fill. It needs higher contrast and lower chroma to remain legible.
For text on a colored button fill (the button label), chroma should be near zero — white (#fff) or near-black (#1a1a1a). Colored button text is almost never appropriate for filled buttons because the chromatic text color will interfere with legibility against the chromatic background. The exceptions are outlined and ghost buttons, where the text color carries the semantic signal.
For chromatic text on a neutral background (outlined/ghost button labels), chroma needs to be high enough that the color reads clearly at text size, but the lightness must be chosen to meet contrast ratios against the background.
At C:0.04, the text looks dark gray — the blue is imperceptible at text size. At C:0.08, a blue tint is faintly visible. At C:0.14, the text is clearly blue — this is the lower threshold for chromatic text that communicates a semantic role. At C:0.20, the blue is vivid and unmistakable.
The minimum chroma for chromatic button text is approximately 0.10–0.12. Below that, the color is not perceivable at 14px body text size and the semantic signal is lost. Above 0.18, the text becomes visually dominant and can compete with filled primary buttons for attention.
Red at even modest chroma levels carries strong semiotic weight — "danger," "error," "stop." A destructive text button at C:0.14 reads clearly as a warning. At C:0.20 it feels alarming, which may be appropriate for confirmation dialogs but too aggressive for inline list actions. The right chroma for danger text depends on context: understated in tables and lists (C:0.12–0.15), assertive in confirmation dialogs (C:0.16–0.20).
Ghost buttons use a lightly tinted fill derived from the semantic color. The chroma of this tint determines whether the button reads as "lightly colored" or "nearly invisible." Too little and it vanishes against the page background. Too much and it competes with filled buttons.
The opacity-based approach from Part II sidesteps this by using the base color at 6–10% opacity over the surface. But when defining tokens explicitly, the tint fill's chroma needs to be approximately 0.02–0.05 at high lightness (L:0.92–0.96). Below 0.02, the tint is imperceptible on most monitors. Above 0.06, the fill becomes a strong visual element that shifts the button's perceived hierarchy upward.
Red ghost fills deserve extra caution — even at very low chroma, a pink-tinted background carries emotional weight:
Even at C:0.03, the red tint is clearly visible and signals danger. This is significantly lower than the threshold for blue. Red requires less chroma to communicate because the hue itself carries strong cultural meaning. The general principle: semantically loaded hues (red, green) need less chroma than neutral hues (blue, purple) to achieve the same communicative impact.
Saturated colors that work perfectly on light backgrounds become visually aggressive on dark ones. The effect is physiological: the eye's adaptation to a dark surround makes any color appear more vivid, and highly saturated hues on dark surfaces produce a perceptual "vibration" — a shimmering instability at the edges of colored elements that causes eye strain.
The right-side buttons maintain the same chroma but increase lightness for visibility on dark backgrounds. The result is they feel more intense, more neon, more fatiguing. This is the vibration problem.
The correction: reduce chroma by 25–40% when shifting to dark mode. Material Design codifies this as moving from tone-600 (higher chroma) in light mode to tone-200 (lower chroma, higher lightness) in dark mode. The resulting colors are pastels — softer, less saturated versions of the light mode fills. They feel lighter and gentler, which is exactly what the eye needs against a dark surround.
Material Design permits one exception to the desaturation rule: small, high-emphasis interactive elements like FABs and primary buttons may retain the light-mode chroma level even in dark mode. The rationale is that at small surface area, the vibration effect is minimal and the saturated color helps the CTA stand out against a field of muted dark surfaces. If you adopt this exception, limit it to a single element per screen — one saturated accent button in a sea of desaturated dark mode surfaces.
The human eye focuses different wavelengths of light at slightly different distances — a phenomenon called chromatic aberration. Blue light focuses farther from the retina than red or green. This means that saturated blue text on dark backgrounds can appear to shimmer or blur at the edges, even for users with normal vision.
The WCAG 3.0 draft directly addresses this: pure blue text on black and pure red text on black should be avoided at high saturation because the eye literally cannot focus both channels simultaneously. Desaturating the text or lightening it (reducing chroma, increasing lightness) resolves the issue. For dark mode button labels that use chromatic text, keep chroma moderate (C:0.08–0.14) and lightness high (L:0.75–0.85).
This is another case where fills and text diverge: a saturated blue button fill on a dark background is acceptable (the fill is a large area, and edges are clear). But saturated blue text on a dark background is problematic because text is thin and the chromatic aberration manifests at the fine edges of letterforms.
Chroma is a hierarchy lever, just like fill mode, size, and weight. Higher chroma commands more attention. Lower chroma recedes. This means you can use chroma variation within a single hue to express the same hierarchy that fill mode (filled → outlined → ghost → text) expresses structurally.
In practice, this is rarely used as the primary hierarchy mechanism because fill mode is more robust and more accessible (it provides shape contrast, not just color contrast). But chroma variation is an excellent secondary hierarchy signal. A secondary outlined button with slightly lower chroma text (C:0.12 vs. the primary's C:0.18) naturally defers to the primary without changing shape or layout.
When your primary blue button and your danger red button coexist on the same screen, they should feel like they belong to the same system. If the blue is at C:0.20 and the red is at C:0.12, the red will feel meek — not an appropriate signal for a destructive action. If the red is at C:0.22 and the blue at C:0.16, the red will dominate visually even when it is supposed to be secondary.
When two semantic colors appear at the same hierarchy level (both filled), they should have chromas that produce equal perceptual intensity. OKLCH makes this straightforward: use the same C value for both, and the perceptual uniformity of the space ensures they feel equivalent. Adjust slightly if one hue hits the gamut boundary — always prefer the lower of the two available maxima.
Borders are the thinnest color element on a button. At 1–2px, color is barely perceivable. This means border chroma needs to be higher relative to its opacity to register as "colored" rather than "gray."
A thin border at 40% opacity and C:0.04 reads as gray regardless of hue. At C:0.10 with the same opacity, a faint color tint is visible. At C:0.18, the border clearly reads as blue. The opacity-chroma interaction is multiplicative: you need either high chroma or high opacity (or both) for a thin border to register as chromatic.
Most design systems use one of two strategies: neutral borders with chromatic text (the text carries the semantic signal; the border is structural), or fully chromatic borders derived from the text color at reduced opacity (border: oklch(from var(--text-color) l c h / 0.3)). The second approach creates a more cohesive color relationship within the button, but the first is simpler and avoids the chroma-at-thin-size legibility issue entirely.
Synthesizing the preceding sections, here is a reference table of recommended OKLCH chroma ranges for each surface in a button system.
| Surface | Light mode chroma | Dark mode chroma | Notes |
|---|---|---|---|
| Primary filled button | 0.14–0.22 |
0.09–0.15 |
The hero CTA. Vivid enough to dominate the visual hierarchy. Reduce 25–40% for dark mode. |
| Danger filled button | 0.14–0.20 |
0.09–0.14 |
Match primary's perceptual intensity. Red's semiotic weight means it can be slightly lower chroma and still feel equally assertive. |
| Chromatic text (outlined/ghost) | 0.12–0.18 |
0.08–0.14 |
Must be high enough to read as colored at text size. Minimum ~0.10 for hue identity. |
| Tinted ghost fill | 0.02–0.05 |
0.02–0.04 |
Very light wash at high lightness (L:0.92–0.96). Red tints register at lower chroma than blue. |
| Chromatic border (outlined) | 0.10–0.18 at 30–50% opacity |
0.08–0.14 at 20–40% opacity |
Thin borders need high chroma or high opacity. Use one or both. |
| Disabled button fill | 0.02–0.05 |
0.01–0.03 |
Barely chromatic. Some systems strip chroma entirely (use neutral gray for all disabled buttons regardless of original color). |
| Neutral button (all variants) | 0.00–0.02 |
0.00–0.02 |
Near-achromatic. Slight warm (H:50) or cool (H:260) tint is acceptable for brand alignment. |
OKLCH makes chroma a first-class design token. Because lightness, chroma, and hue are independent axes, you can define your button color system as a set of hue tokens, a set of lightness tokens per theme, and a set of chroma tokens per role — then compose them.
This architecture lets you adjust the chroma of every button in the system by changing a single token (--C-filled-light). Theme switching redefines the L and C tokens; hue stays constant. The separation of hue, lightness, and chroma into independent tokens is only possible because OKLCH's axes are perceptually orthogonal — changing one does not produce unexpected shifts in the others.
These decisions build on the color palette choices from Part III-A. They refine each color by determining its intensity.
| Step | Decision | Key constraint |
|---|---|---|
| 1 | Check the maximum sRGB chroma for each of your hues at your target lightness | Gamut boundary (varies by hue) |
| 2 | Choose primary button fill chroma (0.14–0.22 for light mode) | Must feel like the dominant interactive element on the page |
| 3 | Match danger chroma to primary's perceptual weight | Equal-intensity semantic colors; OKLCH C values can differ if one hue's gamut is smaller |
| 4 | Set chromatic text chroma (outlined/ghost labels) ≥ 0.10 | Below 0.10, hue identity is lost at body text size |
| 5 | Set ghost tint fill chroma (0.02–0.05 at L:0.92–0.96) | Must be perceptible but subordinate to filled buttons |
| 6 | Set disabled chroma (0.02–0.05 or strip to zero) | Should signal "inactive" without becoming invisible |
| 7 | Reduce all chromas by 25–40% for dark mode fills | Vibration prevention; dark adaptation makes colors feel louder |
| 8 | Test chromatic text in dark mode for chromatic aberration | Saturated blue/red text shimmers on dark backgrounds; reduce C, increase L |
| 9 | Validate perceptual hierarchy: primary > danger > secondary > ghost > disabled | Visual inspection only — no formula captures this |
| 10 | Perform the vibration test on a real device in a dim room | Eye strain and edge shimmer on saturated dark-mode elements |
Part III-B of the Design Primitive Reference series · Continues from Part III-A (Color Palette for Buttons) · April 2026