Skip to content

a11y(tokens): --ui-color-accent-soft-element-red has no dark-context value, failing contrast for both text and focus #473

Description

@IgorShevchik

Found while consolidating focus colours for #191. Filed separately because the fix belongs in the design tokens, not in a theme file, and because it affects more than focus.

The measurement

--ui-color-accent-soft-element-red is a dark red in every theme context:

context value
light #c21b16
dark #b81a15

Against the dark context's --ui-color-bg-content-primary (#262626):

use required measured
focus indicator (WCAG 2.2 SC 1.4.11) 3:1 2.30:1 fails
body text (SC 1.4.3) 4.5:1 2.30:1 fails

In the light context it is fine — 6.07:1 on white.

Where it lands

src/theme/link.ts, the isAction variant — the dashed-underline action link:

'hover:not-disabled:not-aria-disabled:text-(--ui-color-accent-soft-element-red) hover:border-b-(--ui-color-accent-soft-element-red)',
'focus-visible:outline-(--ui-color-accent-soft-element-red)'

So in a dark theme, hovering an action link turns its label a red that does not meet text contrast, and focusing it draws an outline that does not meet indicator contrast.

Why #191 did not fix it

#191 moved nine focus outlines onto --ui-color-design-outline-focused-stroke, which is defined per context and adapts. This one was deliberately left alone: the red is not a stray accent, it matches the hover state, and repainting the focus ring blue while the label goes red would break the variant rather than fix it.

The real defect is upstream of the theme file — the token has one value doing duty in four contexts. --ui-color-accent-soft-element-blue has the same shape (#0154c8 / #0056bf), and #191's measurement found it failing at 2.22:1 on dark for the same reason; that one had a dedicated focus token to move to, and this one does not.

What would fix it

A dark-context value for the token, the way --ui-color-design-outline-focused-stroke has one (accent-main-primary in light and dark, base-6 in the edge contexts). Something in the region of #ff6b66 reaches 4.5:1 on #262626; the actual value is a design decision, not a computed one.

Worth checking the rest of the --ui-color-accent-soft-element-* family in the same pass — the two examined so far both have this shape, and nothing currently tests that a token used as a colour meets contrast in the context it renders in.

Verifying

node -e "const lin=c=>{c/=255;return c<=0.04045?c/12.92:((c+0.055)/1.055)**2.4};
const L=h=>{const[r,g,b]=[1,3,5].map(i=>parseInt(h.slice(i,i+2),16));
return 0.2126*lin(r)+0.7152*lin(g)+0.0722*lin(b)};
const r=(a,b)=>{const[x,y]=[L(a),L(b)].sort((p,q)=>q-p);return (x+0.05)/(y+0.05)};
console.log(r('#262626','#b81a15').toFixed(2))"

Priority: P2 — a real contrast failure, but confined to one variant of one component until the token is reused elsewhere.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions