Home /Color guides /Secondary text: why four in ten fail 4.5:1

Secondary text: why four in ten fail 4.5:1

Not one primary text layer has a problem. 27 secondary text layers do. Within the same palette, two text levels follow two standards, and only one of them was ever checked.

Last updated:2026-10-31

The numbers

69 palettes in this library carry a secondary text layer (textSub), across web and mini program. Sorted by how that layer reads against its background:

  • 27 sit below 4.5:1, which is 39%
  • Low 3.25:1, median 4.76:1, high 7.34:1

For comparison, the primary text layers in those same palettes number 69, of which 0 fall below the threshold, with a low of 12.96:1 and a median of 14.66:1.

That contrast is where this whole article starts: one library, one set of palettes, one designer, zero defects in the primary text layer and 39% in the secondary one.

Why primary text is fine

Because primary text is treated as a hard requirement. Every palette clears a 4.5:1 gate before it enters the library, and one that fails is dropped. The lowest primary text here is 12.96:1 and the median is 14.66:1, close to black on white. That is not a design technique, it is the gate doing its job.

Secondary text has no such gate. It counts as supporting information, and in a lot of design systems it gets filed under typography rather than legibility. So it is set by eye instead of by ratio. A designer opens the mockup on a bright monitor, picks a lighter gray because it reads as more refined, and it does look better.

The catch is that the mockup gray is judged on a screen, and the real context is not that screen. The same gray that feels quiet indoors becomes a blur on a phone outdoors. And this layer tends to carry exactly the details people need to confirm: timestamps, units, captions, field names.

What the worst ones look like

The five lowest in the library are all mini program palettes, all below 3.7:1:

PaletteSecondaryBackgroundRatioPrimary text
Fresh orange#8a8a8a#f7f8fa3.25:115.5:1
Business blue#7a879b#f5f7fa3.39:115.3:1
Beauty pink#9a8089#fdf8fa3.43:115.1:1
Tea green#7c8b82#fafcfa3.47:114.4:1
Pet purple#857e9c#f8f7fd3.61:115.4:1

The #8a8a8a in the first row deserves its own paragraph. In design tools it is practically the default supporting gray, the one that gets reached for without thinking. Against pure white it lands near 3.5:1, against any tinted light background it goes lower, and against #f7f8fa it arrives at 3.25:1.

Note also that all five have primary text between 14 and 15.5:1. These designers know how to hit contrast. They treated body text and supporting text as two different problems, and applied a standard to only one of them.

Can you actually use 3.25:1

Yes, for large text only. WCAG splits it like this:

  • 4.5:1 for normal body text. A 14px or 16px caption lives here.
  • 3:1 for large text, meaning 18.66px bold or 24px regular.

So 3.25:1 means the text has to be at least 24px, or 18.66px bold. Set timestamps, units and form hints at that size and the hierarchy collapses, at which point calling the layer secondary makes no sense.

Put another way: pale and small are two payments out of the same budget, and you cannot spend both. Pale requires large. Small requires dark. Most of the broken designs spend both.

Three fixes, cheapest first

  1. Darken the layer to 4.5:1 and carry the hierarchy with size and weight. One color value changes. You do lose some of the softness, and you gain a layer that reads on every screen. Push the textSub of those five palettes toward the primary text and they mostly clear the line, with less visible difference than expected.
  2. Keep it pale, raise it past 16px, and lock its use. Right for the few places where legibility genuinely can be traded away, such as a decorative overline. The condition is that the layer must never carry information someone has to read.
  3. Delete the layer. If an interface uses secondary text in two or three places, the primary color plus spacing and grouping will do the job. One less role is one less thing to fail later.

The first is right for almost every project, for a simple reason: supporting information is secondary because it matters less, not because it can be hard to see. Those two get conflated constantly.

Three other places with the same problem

Secondary text is only the most visible instance. The next three come from the same root: a color chosen by eye that was never treated as a legibility metric.

  • Input placeholders. Plenty of implementations reuse the secondary text color, copying the 3.25:1 problem straight onto the first thing a user has to fill in. Worse, the placeholder is often the only hint in the field and it disappears the moment typing starts, so failing to read it means no hint at all.
  • Small text on buttons. White on the primary, and it goes wrong as the size drops. A 4.5:1 primary against white means white on the primary is roughly the same, but 12px button text needs that ratio plus real weight, and many implementations only reduce the size.
  • Labels inside charts. Axis ticks and legends, usually two steps smaller than body text and set in the same secondary gray. Fine on a monitor, gone on a meeting room projector.

What these share with secondary text: none of them count as content, so none of them entered the content legibility check, and all of them are text users must read. Build the checklist around what a user must read rather than what counts as body copy, and this whole group surfaces at once.

A one-minute check in the mockup beats a fix in code

Everything above costs design tokens, fresh screenshots and a review round once it reaches code. Caught in the mockup, it costs one color value. Two actions, a minute each:

  1. Screenshot the design and drop the brightness to thirty percent. That approximates outdoors or a dimmed screen. If secondary text smears at that point, it smears in real use. This is closer to a user's actual situation than checking a ratio, because it dims every color at once.
  2. Convert the screenshot to grayscale. If the hierarchy vanishes, the palette was leaning on hue rather than lightness. Hue differences do not exist for people with color vision deficiency (roughly 8% of men have some form), so the hierarchy still has to hold in gray.

Both actions measure the same thing: not whether the numbers pass, but whether the information survives. Passing a ratio is necessary and not sufficient. An interface where every contrast value is compliant but the hierarchy is muddled still leaves users unable to find the primary button.

Where the numbers come from

Every figure above is computed from the palettes in this library using the WCAG 2.1 relative luminance formula, not estimated by eye. Of the 69 secondary text layers, 27 fall below 4.5:1. The thresholds and the calculation are documented in the editorial standard, and the full library sorted by contrast is on the accessibility overview.

If you want to check a palette of your own, each platform page lists that palette's secondary text contrast directly, so you do not have to work it out by hand.