Skip to content

a11y(tokens): the focus stroke cannot reach 3:1 in the edge contexts #476

Description

@IgorShevchik

Found while reviewing #474, which moves nine focus outlines onto --ui-color-design-outline-focused-stroke. That is a clear fix in the light and dark contexts and a regression in the edge ones, and the regression is a property of the token rather than of how #474 uses it.

What the token resolves to

context value
light var(--ui-color-accent-main-primary)#0075ff
dark var(--ui-color-accent-main-primary)#1587fa
edge-light var(--ui-color-base-6)#00000059 — black at 35%
edge-dark var(--ui-color-base-6)#ffffff66 — white at 40%

The measurement

Composited over each context's default --air-theme-bg-color, and over the component surface (--ui-color-bg-content-primary, itself translucent in the edge contexts):

vs backdrop vs surface
edge-light, focus token 2.41 2.11
edge-light, soft-element-blue (what #474 replaced there) 6.05 5.30
edge-dark, focus token 2.46 1.67
edge-dark, outline-primary (replaced) 3.34 2.27

WCAG 2.2 SC 1.4.11 asks 3:1. The token does not reach it in either edge context, against either adjacent colour. For four components — Accordion, Splitter and Table — edge-light is a move from 6.05:1 to 2.41:1.

Why it is not a bug in #474

An edge context paints over a backdrop the portal chooses — that is what the context is for. So no opaque colour has a guaranteed ratio there either: soft-element-blue's 6.05:1 is measured against one particular default value, and the same colour against a dark background image would be far worse than the token is. A translucent luminance shift degrades evenly rather than gambling on the backdrop.

The open question is whether evenly mediocre is good enough, and 2.4:1 says it is not. 40% white over an arbitrary backdrop cannot reach 3:1 by construction — the alpha would have to rise, or the stroke would need a second contrasting layer.

What might fix it

Not something a theme file can do, which is why this is filed against the tokens:

  • Raise the alpha. White at 40% over #545d6b gives 2.46:1; it needs roughly 65–70% to clear 3:1 against that particular backdrop, and still nothing against every backdrop.
  • A two-tone stroke — the approach browsers themselves use for the default focus ring, an inner light and an outer dark line, so one of the two always contrasts. This is the only option that works against an unknown backdrop, and it is a shape change rather than a colour one.
  • Accept and document it as a known limitation of edge themes, with the reasoning written down so it is not rediscovered every year.

Reproducing

node -e "
const lin=c=>{c/=255;return c<=0.04045?c/12.92:((c+0.055)/1.055)**2.4};
const L=a=>0.2126*lin(a[0])+0.7152*lin(a[1])+0.0722*lin(a[2]);
const h=s=>[1,3,5].map(i=>parseInt(s.slice(i,i+2),16));
const over=(f,a,b)=>f.map((c,i)=>c*a+b[i]*(1-a));
const r=(a,b)=>{const[x,y]=[L(a),L(b)].sort((p,q)=>q-p);return (x+0.05)/(y+0.05)};
const bg=h('#545d6b');
console.log('edge-dark token :', r(over(h('#ffffff'),0.40,bg),bg).toFixed(2));
console.log('edge-light token:', r(over(h('#000000'),0.35,h('#eef2f4')),h('#eef2f4')).toFixed(2));"

Related: #473, the same shape in --ui-color-accent-soft-element-red — a token whose one value cannot serve every context it renders in.

Priority: P2 — a measured failure in two of four contexts, on components a keyboard user has to be able to see focus on.

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