Your tinted background is not actually tinted
The most common line in a design handoff is "background at 5% of the primary". Of the 100 palettes here, 38 have a background that HSL calls tinted and only 9 look tinted. The gap is not a mistake in those palettes; it is the wrong instrument.
The 5% in "background at 5% of the primary" has no unit
The problem is not the number, it is the unit. Five percent opacity means the primary diluted with white. Five percent of HSL saturation is a different color. Five percent of a lightness shift is a third one. The three can differ in visual strength by more than 3x, and the handoff note reads "5%" in all three cases.
Worse, most design tools show HSL saturation. HSL defines saturation relative to the current lightness, so the closer lightness gets to 100, the less it does. The numbers below come from the background color of all 100 palettes in this library, and the two measures disagree in opposite directions.
Same backgrounds, a 4x gap between two measures
| Measure | Counted as tinted | Conclusion it produces |
|---|---|---|
| HSL saturation > 12 | 38 | Nearly four in ten backgrounds carry color |
| CIELAB chroma C* ≥ 2 | 9 | Fewer than one in ten look tinted |
Recompute those 38 palettes by chroma and the median lands at 1.03, which means that placed next to pure white, almost nobody can tell them apart. Their median HSL saturation is 9.1, and a few of them read 100.
The library-wide distribution is starker. 75 palettes sit below C* 1 (75%), 16 fall between 1 and 2 (16%), and only 9 reach 2 (9%), with a median of 0.63.
In other words, a background labeled "a touch of the primary" is white to the people looking at it, nine times out of ten. That is not a defect, but if the intent was a visible zone, the intent did not ship.
Why HSL saturation collapses in the light end
HSL saturation is relative: it describes how far a color sits from the neutral axis, and that distance is compressed as lightness approaches white. At L 98 the three RGB channels all crowd above 250, the spread between max and min drops to single digits, and saturation has no room left to express itself.
Here is one hue at h=30, lightness fixed per row, saturation pushed from 30 to 90:
| Lightness | Chroma C* for S=30 / 60 / 90 |
|---|---|
| 98% | 1.1 / 1.8 / 2.9 |
| 95% | 2.5 / 4.6 / 7.1 |
| 90% | 4.8 / 9.9 / 14.5 |
Each row lists chroma C* for S=30, 60 and 90 in that order. Two things stand out.
First, at L 98, tripling saturation from 30 to 90 moves chroma from 1.1 to 2.9, still inside the range nobody sees. A 3x increase in the number the tool shows buys almost no visible change.
Second, change nothing but lightness, dropping 98 to 90, and S=30 climbs from 1.1 to 4.8, more than four times what S=90 manages at L 98. In the light end, lightness decides how tinted a color looks and saturation is the minor variable. To add a hint of color, go down in lightness rather than up in saturation.
One 5%, two implementations, 300x apart
The real danger is that both implementations that claim to be "5% of the primary" land in entirely different orders of magnitude. Here is one real primary from the library, #2563eb, done both ways:
| Implementation | Resulting color | Chroma C* |
|---|---|---|
| A: primary at 5% opacity over white | #f4f7fe | 3.72 |
| B: saturation at 5% of the primary, lightness raised to 98 | #fafafa | 0.01 |
A lands inside the visible window and is usable as is. B comes out #fafafa at chroma 0.01, indistinguishable from plain gray, which is the same as not doing it. The two are 323 times apart.
Neither is wrong. A happens to work because diluting with white lowers saturation and raises lightness at the same time: two variables move together. B lowers saturation and leaves lightness alone, so nothing happens. Same percentage, same request, one inside the window and one outside it, and the design file says the same five characters either way.
The window that actually works
Measured against this library and the demonstration above, chroma splits into four bands, each with a use:
| Chroma C* | What it looks like | Where it belongs |
|---|---|---|
| < 1 | White or gray; swapping it goes unnoticed | If you want white, say white instead of "2% of the primary" |
| 1 to 2 | Nothing you can name, but the swap registers | Large page backgrounds |
| 2 to 4 | Visibly tinted, still restrained | Section bands, card surfaces, table headers |
| > 4 | Unambiguously colored | Small areas, or dark backgrounds |
The highest chroma in the whole library is #0f0d1a at 8.4, and it is a dark background. Among the light ones nothing reaches 7 without already reading as clearly colored. The usable window in the light end is narrow: 2 to 4.
Dark backgrounds are a different problem
The 2 dark backgrounds here measure 7.0 to 8.4, above the ceiling of the light ones, and none of them looks garish. Lightness again: in the dark end the eye resolves color less finely, so the same absolute difference needs more chroma to register, which leaves dark surfaces room for more color.
One more comparison makes the same point. The median chroma of body text in this library is 4.6, more than seven times the median background at 0.63. Text is the smallest element on screen and the one that must be read, so its chroma has to be strong; the background is the largest and the one that must recede, so its chroma has to be weak. The same rule produces opposite numbers at the two ends.
There is no single answer for how much chroma a background should carry, because area decides. Dark, small, needs attention: go higher. Light, large, needs to recede: go lower.
Putting it to work
- Judge by chroma, not HSL saturation. C* is the square root of a squared plus b squared in CIELAB under D65. Every color library ships it, in about twenty lines. HSL saturation is a rough proxy in the middle of the lightness range, and backgrounds are not in the middle.
- For a lightly tinted light background, hold lightness at 95 to 97 and chroma at 2 to 3. The two numbers are coupled: lower lightness makes the same chroma more visible. Set lightness first, then tune chroma.
- Hand off absolute values, not percentages. "5% of the primary" becomes three or four different colors downstream, because everyone reads 5% differently. Ship
#f7f8faand note that it is a cool neutral at chroma 1.8. Nothing is ambiguous.
The third item is the one people skip, and it decides whether the first two matter. A value that reproduces beats a percentage that sounds precise.
Where the numbers come from
Every figure above is computed at build time by src/lib/guide-stats.mjs from the bg field of all 100 palettes, with the CIELAB conversion in src/lib/lab.mjs using a D65 white point and the standard sRGB matrix. The band edges at 1, 2 and 4 were chosen from this distribution rather than quoted from a standard, because no standard specifies how much chroma a background should carry.
The nine chroma values in the demonstration table are generated by formula at build time with the hue fixed at 30. They do not depend on any palette, so they recompute to the same numbers forever.
Related: cards and dividers covers the other family of roles that measure low but still exist; the editorial standard documents the gates and the calculation.