Repository navigation
fix(a11y): give operable controls a 3:1 border (WCAG 1.4.11) - #72
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
A prospective user said: "Text on pictures 1 and 2 will most likely fail accessibility standards / checks (not enough contrast). If I need ui library in production it should pass a11y standards." Their specific claim was wrong and the surrounding point was right. TEXT contrast passes everywhere, measured with WCAG relative-luminance maths on every shipped pair: muted text 6.12:1 on background and 5.54:1 on surface in dark, 8.45:1 / 8.10:1 in light; body copy 16.91:1 / 17.93:1. AA throughout, AAA mostly. What failed is non-text. `--border` is 1.46:1 on the page in dark and 1.19:1 in light, against 1.4.11's 3.0 minimum — and it is the ONLY thing identifying a control, because `--surface` against `--background` is about 1.1:1, so the fill distinguishes nothing either. A search input, a sort select, a category chip and the save button on every card were boundaries nobody with low vision could locate. Fixed with a SECOND token, not a louder `--border`. 1.4.11 exempts decorative edges, and most uses here are decorative: 1px rules, card outlines, the preview dot grid, and 1318 uses inside registry/ components. Raising `--border` would have passed the criterion by restyling the entire published product. `--border-control` is #646464 in dark and #919191 in light — the lowest-contrast greys clearing 3.0:1 against BOTH `--background` and `--surface` (3.35/3.03 and 3.15/3.02), so the visual jump is as small as the criterion allows. registry/** is untouched. Measured after: 1150 bordered controls across both themes on the homepage, 0 failing, and 0 failing on /install, /submit, /account, /review, /categories and a component page. THE CHECK LIED TWICE BEFORE IT WORKED, which is the part worth recording. Version 1 hand-picked four selectors and reported PASS on two elements whose border-width was 0px — there was nothing to measure. Version 2 swept everything and reported "0 of 575 failing" while the category chips sat at 1.46:1: it parsed colours with /\d+/g, so a backdrop of `oklab(0.144787 0.00000661612 0.00000289828 / 0.85)` became [0, 144787, 0], produced nonsense luminance, and scored as passing. I believed that green and nearly shipped on it. The version used here resolves every colour through a canvas, which normalises any CSS colour function, composites translucent backdrops over what shows through them, and ABORTS rather than scoring a value it cannot parse. Mutation-proven: reverting the save button's single line takes it from 0 failures to 1084, restoring it returns 0. The 1084 is one component rendered once per card, which is also why the raw failure count looked alarming and was in fact two components. Three detection gaps are worth naming so the next sweep does not repeat them: a 12-line look-back for the owning element missed `catalog-controls.tsx` entirely, a lowercase `<a` pattern missed every `<Link>`, and a `\b` after a chip label never matched because the text is `Heroes75` with no space. Looked at in both themes at 1440x900: controls read as controls, the page does not read as louder. Signed-off-by: Nik <84yk8btb9f@privaterelay.appleid.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nikolas-sapa
force-pushed
the
fix/a11y-control-borders
branch
from
October 13, 2026 12:42
3349bee to
4bf6652
Compare
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The report
Their specific claim was wrong and the surrounding point was right.
Text contrast passes everywhere
Measured with WCAG relative-luminance maths on every shipped pair:
What actually failed is non-text
--borderis 1.46:1 on the page in dark and 1.19:1 in light, against WCAG 1.4.11's 3.0 minimum — and it is the only thing identifying a control, because--surfaceagainst--backgroundis about 1.1:1, so the fill distinguishes nothing either.A search input, a sort select, every category chip and the save button on every card had boundaries nobody with low vision could locate.
Fixed with a second token, not a louder
--border1.4.11 exempts decorative edges, and most uses here are decorative: 1px rules, card outlines, the preview dot grid, and 1318 uses inside
registry/components. Raising--borderwould have passed the criterion by restyling the entire published product.--border-controlis#646464dark /#919191light — the lowest-contrast greys clearing 3.0:1 against both--backgroundand--surface(3.35/3.03 and 3.15/3.02), so the visual jump is as small as the criterion allows.registry/**is untouched.After
1150 bordered controls across both themes on the homepage, 0 failing. Also 0 on
/install,/submit,/account,/review,/categoriesand a component page.The check lied twice before it worked
This is the part worth recording.
Version 1 hand-picked four selectors and reported PASS on two elements whose
border-widthwas0px— there was nothing to measure.Version 2 swept everything and reported "0 of 575 failing" while the category chips sat at 1.46:1. It parsed colours with
/\d+/g, so a backdrop ofoklab(0.144787 0.00000661612 0.00000289828 / 0.85)became[0, 144787, 0], produced nonsense luminance, and scored as passing. I believed that green and nearly shipped on it.The version used here resolves every colour through a canvas (which normalises any CSS colour function), composites translucent backdrops over what shows through them, and aborts rather than scoring a value it cannot parse.
Mutation-proven: reverting the save button's single line takes it from 0 failures to 1084; restoring it returns 0. That 1084 is one component rendered once per card — which is also why the raw count looked alarming and was in fact two components.
Three detection gaps, named so the next sweep doesn't repeat them: a 12-line look-back for the owning element missed
catalog-controls.tsxentirely; a lowercase<apattern missed every<Link>; and a\bafter a chip label never matched, because the text isHeroes75with no space.Verification
npx tsc --noEmit— exit 0npm run test:source-invariants— 0 problems🤖 Generated with Claude Code