"The Forecast" went live yesterday. The accessibility pass that followed found three contrast failures, each measured against the background it actually renders on. Nothing on this page is live, and nothing here should be until you have looked at it and picked an option.
Each failure gets two or three candidate fixes below. Every ratio is computed for both themes, with the maths shown, not eyeballed. Each option carries one paragraph on what it costs and what it keeps.
The fence stays in force. Band colours mean the score. The
sky owns page chrome. --chart-1/2/3 own line identity. Nothing else gets a colour.
Every option below moves an existing token along its own hue, changes a chip's fill or size
instead of its hue, or reuses the sky, the token that already owns chrome. None of them give the
projected chip a band colour, and none of them touch assertProjectedRender() in
build/prerender.js, the function that would fail the build if one did.
--mixed and --zeroThe band word is the second signal for a score. It has to work even when a reader cannot tell
amber from clay. Today it fails that job in two places. --mixed reads 4.21:1
against --panel in light theme, short of the 4.5:1 bar for text. --zero
is worse: 3.31 to 3.83:1 in light theme,
3.98 to 4.68:1 in dark, failing on panel and
card in both themes.
| --mixed | paper / panel / card |
|---|---|
| Today #a06400 | mixed4.59--mixed mixed4.21--mixed mixed4.86--mixed |
| A · retune the token #975e00 | mixed5.06--mixed mixed4.64--mixed mixed5.36--mixed |
| B · fill the chip #ffeccc fill, #29241c text | mixed13.29--mixed mixed13.29--mixed mixed13.29--mixed |
| C · large text, same hex #a06400 (unchanged, 1.167rem+) | mixed4.59--mixed mixed4.21--mixed mixed4.86--mixed |
| --zero | paper / panel / card |
|---|---|
| Today #8a8177 | not yet3.61--zero not yet3.31--zero not yet3.83--zero |
| A · retune the token #716a62 | not yet5.03--zero not yet4.61--zero not yet5.33--zero |
| B · fill the chip #e7e6e4 fill, #29241c text | not yet12.35--zero not yet12.35--zero not yet12.35--zero |
| C · large text, same hex #8a8177 (unchanged, 1.167rem+) | not yet3.61--zero not yet3.31--zero not yet3.83--zero |
| --mixed | paper / panel / card |
|---|---|
| Today #e0a53f | mixed8.17--mixed mixed7.49--mixed mixed6.95--mixed |
| A · retune the token #e0a53f (unchanged) | mixed8.17--mixed mixed7.49--mixed mixed6.95--mixed |
| B · fill the chip #4f370d fill, #f2e9db text | mixed9.26--mixed mixed9.26--mixed mixed9.26--mixed |
| C · large text, same hex #e0a53f (unchanged, 1.167rem+) | mixed8.17--mixed mixed7.49--mixed mixed6.95--mixed |
| --zero | paper / panel / card |
|---|---|
| Today #8d8172 | not yet4.68--zero not yet4.29--zero not yet3.98--zero |
| A · retune the token #978c7f | not yet5.41--zero not yet4.96--zero not yet4.60--zero |
| B · fill the chip #332e29 fill, #f2e9db text | not yet11.16--zero not yet11.16--zero not yet11.16--zero |
| C · large text, same hex #8d8172 (unchanged, 1.167rem+) | not yet4.68--zero not yet4.29--zero not yet3.98--zero |
The smallest move on the table. Darken --mixed and --zero along
their own hue and saturation until they clear 4.5:1 on the worst surface, with headroom to about
4.6. --mixed moves from #a06400 to #975e00,
light theme only, since it already clears 4.5:1 everywhere in dark. --zero moves in
both themes: #8a8177 to #716a62 in light,
#8d8172 to #978c7f in dark. --good and
--long stay exactly where they are. I would want --zero's new dark value
checked next to the other three bands before it ships, since it lands close to
--muted-strong, and the four-band ramp needs to keep reading as a brightness
gradient, not just a hue change.
Instead of moving the hue, give the chip a solid fill in the band colour and swap the text to
--ink. Contrast stops being a live constraint: 9 to 13 times what it needs, against
any of these tints. This is the biggest visual change of the three. The identity signal moves from
coloured text to a coloured fill, and consistency probably means restyling all four band chips at
once, not just the two that are actually failing. The fill is also weak on its own terms: it
measures only 1.0 to 1.5:1 against the surrounding panel, so without the border doing real work,
the tint is close to invisible against the page it sits on.
WCAG grades bold text at 14pt (18.67px) or larger against a 3:1 bar, not 4.5:1. Every current
band value already clears 3:1 — --zero at its lowest is
3.31:1. So this option changes no colour at all. It takes the chip
from .72rem (11.52px) to at least 1.167rem (18.67px) and leaves every hex
value alone. The cost is size, not colour: a chip 62% larger next to every score, on every card,
table row and roadmap step on the site, and every layout that assumed today's footprint needs a
second look.
--proj--proj reads 3.77:1 against
--proj-bg in light theme. Dark theme already clears 4.5:1, at
5.75:1. This chip is the one place the fence is written into code.
assertProjectedRender() in build/prerender.js fails the build if a
projected number ever carries a band class, a [data-band], or a
var(--band) reference, because green, amber and clay mean measured, and nobody has
measured a fleet that has not been built yet. None of the three options below touch that rule.
They move --proj and --proj-bg, and neither is a band token.
| --proj | --proj-bg (or host surface) |
|---|---|
| Today #7c7568 on #efe9dc | projected3.77--proj / --proj-bg |
| A · darken text only #6e675c on #efe9dc (bg unchanged) | projected4.62--proj / --proj-bg |
| B · lighten the wash, nudge the text #777064 on #faf8f4 | projected4.62--proj / --proj-bg |
| C · drop the tint (near-miss) #7c7568 (unchanged) on host surface | projected4.31--proj on paper projected3.95--proj on panel projected4.56--proj on card |
| --proj | --proj-bg |
|---|---|
| Today (already passes) #a99e8e on #2b251d | projected5.75--proj / --proj-bg |
Dark theme already clears 4.5:1. None of the three options change it.
The same approach as the band chip's option A. Hold --proj-bg still and darken
--proj until it clears 4.5:1. It lands at 4.62
with a little headroom, moving from #7c7568 to #6e675c.
Dark theme is untouched. The projected label gets visibly heavier against its own soft
background, and that softness is the cue that a number has not happened yet. I would expect this
to be the change a returning reader notices first.
Puts most of the work on the background instead of the text. Lighten --proj-bg
toward white and nudge --proj only a little: the text moves 1.76 lightness points
against 5.1 for option A. --proj-bg also fills the roadmap ring and the progress-tape
background, so lightening it moves those surfaces too. By the time the ratio clears 4.6:1, the
wash itself sits only a few lightness points short of --card. The muted note that
marks a chip as not-yet-measured nearly disappears.
The cheapest change in code: delete the background rule and let --proj sit on
whatever surface hosts it. It clears 4.5:1 against --card
(4.56:1). It does not clear 4.5:1 against --paper
(4.31:1) or --panel
(3.95:1). I am listing it because it is on the table, not because
it works: it fixes the failure on one of the three surfaces the chip actually renders on and
leaves it broken on the other two.
--line--line is the only border on every text input, select and textarea. WCAG 1.4.11
wants 3:1 for a control's boundary. Decorative dividers are exempt, so this is a UI-component
failure, not a text failure. Today --line measures 1.20
to 1.43:1 across both themes, against all four surfaces it can sit
on: --paper, --panel, --card, --field. It is
the easiest of the three to fix cleanly. --line on a form control is not a band, is
not the sky, and is not a chart line. It is the one token here with no fence at all.
| --line | paper / panel / card / field |
|---|---|
| Today #e3daca | 1.31--line on paper 1.20--line on panel 1.39--line on card 1.39--line on field |
| A · scoped --field-border #91794d | 3.93--line on paper 3.60--line on panel 4.16--line on card 4.16--line on field |
| B · reuse --sky at full strength #2d5a7d | 6.90--line on paper 6.33--line on panel 7.32--line on card 7.32--line on field |
| C · muted, desaturated sky #475763 | 7.05--line on paper 6.46--line on panel 7.47--line on card 7.47--line on field |
| --line | paper / panel / card / field |
|---|---|
| Today #3a332a | 1.43--line on paper 1.31--line on panel 1.22--line on card 1.31--line on field |
| A · scoped --field-border #8a7964 | 4.24--line on paper 3.89--line on panel 3.61--line on card 3.89--line on field |
| B · reuse --sky at full strength #9dc2dc | 9.49--line on paper 8.70--line on panel 8.07--line on card 8.70--line on field |
| C · muted, desaturated sky #b1bec8 | 9.39--line on paper 8.61--line on panel 7.99--line on card 8.61--line on field |
A new token, scoped to form controls only, kept on --line's own hue and darkened
until the worst surface clears 3:1 with headroom (--panel in light,
--card in dark). Light moves to #91794d, dark to
#8a7964. Every decorative divider, card edge and ghost-button border keeps
using --line exactly as it does today. The cost: a second token that means close to
the same thing as the first. Two greys called "the border colour" is a real maintenance risk if a
future edit touches one and forgets the other.
--sky already exists, already owns page chrome, and is already the colour on a
focused input: :focus-visible and .srch input:focus both use it.
Extending it to the resting border adds no fourth owner and clears 3:1 by more than double, in
both themes. The cost is specific. If the resting border and the focus border are the same
colour, focus stops reading as a state change, and that trades this failure for WCAG 2.4.7, focus
visible.
Keeps --sky's hue family, still chrome, still zero new owners in the fenced
sense, but cuts saturation to about a third and retunes lightness. The result reads as a muted
steel-blue rather than the vivid link-blue on the focus ring. It clears 3:1 by the same wide
margin as option B, without option B's collision. The cost is one more derived value to define
and document, and the same scrutiny a scoped token would get over whether it is really not a new
owner.
Failure 1, option A. Retune the two tokens. It is the smallest move, keeps the outline chip exactly as approved, and touches only the two hex values that are actually failing.
Failure 2, option A. Darken the text. Same reasoning as failure 1, and it is the only
one of the three that clears 4.5:1 on the surface the chip actually uses without also moving
--proj-bg's other jobs.
Failure 3, option A. The scoped --field-border token. It avoids the focus
collision in options B and C and leaves --line's decorative meaning exactly where it
was.
--good and --long clear 4.5:1 everywhere in both themes already.
No option here changes them.--chart-1/2/3) measured 4.12 to 9.23:1 against panel in the
last pass. Still fine, still untouched.--sky,
which already exists. None of them propose a new hue.