Skip to content

Contrast: contrastFor="ui" checks non-text contrast (WCAG 1.4.11, 3:1) - #11

Open
palatinus-sb wants to merge 1 commit into
rishimohan:mainfrom
palatinus-sb:feat/non-text-contrast
Open

palatinus-sb wants to merge 1 commit into
rishimohan:mainfrom
palatinus-sb:feat/non-text-contrast

Conversation

@palatinus-sb

Copy link
Copy Markdown

What and why

contrastWith only checks text contrast today. The color area draws its line at 4.5:1 and the badge shows AAA / AA / AA Large / Fail. Borders, icons, focus rings and other UI parts fall under WCAG 1.4.11 (non-text contrast) instead, which needs 3:1 and only has pass or fail. This PR adds an opt-in way to check for that.

contrastFor="ui", on ColorPicker, ColorField, Picker.Area and Picker.Contrast, in React and Vue:

  • The area line is drawn at 3:1 instead of 4.5:1.
  • The badge shows Pass or Fail. It reuses data-level="pass" / "fail", so there are no CSS changes.
  • The default, "text", behaves exactly as before.
<ColorPicker value={border} onChange={setBorder} modes={["solid"]} contrastWith="#fff" contrastFor="ui" />

Core:

  • contrastLevel(ratio, "ui") returns "Pass" | "Fail". Overloads keep the old return type for calls without a target.
  • New contrastMinimum(target) returns 4.5 or 3. React and Vue use it for the line, so the threshold isn't hard-coded twice.
  • New exported type ContrastTarget = "text" | "ui".

Fix to the existing line: the area line used to stop at the last sampled column that had a crossing. Where the boundary leaves through the top or bottom edge between two columns, that left the line a few pixels short of the edge. A 3:1 line makes this easy to see. The line now ends exactly on the edge, found by bisection along it. I checked every hue at both thresholds against white, black and gray backgrounds: all 2,099 lines start and end on an edge.

Tests:

  • Core unit tests for both targets.
  • A harness cell (at the end of the page, so the perf picker doesn't move) shows the same color checked both ways.
  • An e2e test covers the badge level, the line position and the line reaching the top edge.

Docs: llms.txt, SKILL.md, the READMEs and the docs site pages are updated.

Checklist

  • pnpm typecheck and pnpm --filter @colorshot/core test pass
  • pnpm --filter @colorshot/playground test:e2e passes, for UI changes
    • Run on Chromium and Firefox. WebKit was left to CI because I'm on Windows.
    • A few Firefox tests failed now and then on a busy local machine and passed when re-run. One of them, a page.goto timeout in css-resets, fails the same way on main.
  • pnpm build && pnpm size stays within budget
    • core 13.3 / 13.4 KB, react 23.6 / 23.7 KB, vue 23.9 / 24.1 KB
  • React and Vue behave the same (props, data-part names, behavior)
  • New behavior is behind an opt-in prop; defaults are unchanged
  • Changeset added (pnpm changeset) if the published package changes
  • llms.txt and skills/colorshot/SKILL.md updated if a prop or export changed

…) in the badge and the area line; the area line now reaches the edge it leaves through

contrastFor="ui" on ColorPicker, ColorField, Picker.Area and Picker.Contrast (React and Vue) checks
contrastWith for borders, icons and focus rings: the area line moves to 3:1 and the badge shows Pass / Fail.
The default ("text") is unchanged. Core adds contrastLevel(ratio, "ui"), contrastMinimum(target) and the
ContrastTarget type.

The area line used to stop at the last sampled column with a crossing, a few pixels short of the top or
bottom edge; it now ends on the edge.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant