Contrast Picker

Shows the WCAG contrast ratio of a text and background pair, never rounded up, and offers the nearest passing colour, so unreadable text doesn't ship.

StopsUnreadable text colour shipped

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/contrast-picker.json

The files land in your project. They're yours to edit.

Usage

contrast-picker-usage.tsx
import { ContrastPicker } from "@/components/ui/contrast-picker"

return <ContrastPicker defaultValue={{ foreground: "#777777", background: "#ffffff" }} />

What it catches

  • Ratios just under a threshold that rounding would hide (4.499 shown as 4.49, a fail)
  • Failures for normal text versus large text, at AA and AAA
  • A nearest passing colour that keeps the hue

Props

ContrastPicker props
PropTypeDefault
value

Controlled pair.

ColorPair—
defaultValue

Initial pair when uncontrolled.

ColorPair{ foreground: "#777777", background: "#ffffff" }
onValueChange

Called with the pair on every change, including one-tap fixes.

(value: ColorPair) => void—
level

WCAG level the warning and fixes aim for.

"AA" | "AAA""AA"
textSize

Whether the pair is used for body text or large text (24px, or 18.66px bold).

"normal" | "large""normal"
foregroundLabel

Label of the text colour.

string"Text"
backgroundLabel

Label of the background colour.

string"Background"
bodyLabel

Short name of normal-size text in the badges.

string"body"
largeLabel

Short name of large text in the badges.

string"large"
previewText

Sample text in the preview.

string"Your order has shipped"
failMessage

Builds the warning shown when the pair falls short.

(level: string, size: string, needed: number) => string(lvl, size, needed) => `Fails ${lvl} for ${size === "norm…
fixPrefix

Start of the fix buttons' accessible names ("Use #767676 for text").

string"Use"

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Labelled colour fields with aria-invalid on unreadable values; swatches have aria-labels
  • The ratio has an aria-label ("Contrast ratio 4.47 to 1")
  • Pass/fail is written in words ("fails, needs 4.5:1") plus icons
  • Warnings are aria-live="polite"

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • Uses the WCAG 2 formula; it is not an APCA (WCAG 3 draft) checker
  • Checks solid colours only, not text over images, gradients or transparency
  • Readability also depends on font weight and size, which it does not measure

Rules

Rules last verified:

Informational. Checked thresholds: AA 4.5:1 normal / 3:1 large; AAA 7:1 normal / 4.5:1 large. Copied components don't update themselves, so re-check after rule changes (see updating a component).

What it stops

Unreadable text colour shipped

Also known as

contrast · wcag · color picker · accessibility · a11y · aa aaa · theme · brand color

Field type

Dependencies

Pairs well with