# Contrast checker

> Text and background pickers with a live preview, the WCAG ratio and AA and AAA results.

A shadcn/ui block from the [@reui](https://reui.io) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/reui/c-color-picker-13).

- Page, with a live preview: https://shadcn.exchange/r/reui/c-color-picker-13
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/reui/c-color-picker-13-light-6d0a2df7bb1a8384.png
- Registry item: https://reui.io/r/new-york-v4/c-color-picker-13.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://reui.io/r/new-york-v4/c-color-picker-13.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@reui/badge`, `button`, `card`, `@reui/color-picker`, `field`, `@reui/frame`

## Files

### `c-color-picker-13.tsx`

```tsx
"use client"

import { useState } from "react"
import type { CSSProperties } from "react"
import { Badge } from "@/components/reui/badge"
import {
  ColorPicker,
  getContrastRatio,
} from "@/components/reui/color-picker"
import { Frame, FramePanel } from "@/components/reui/frame"

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Field, FieldLabel } from "@/components/ui/field"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"

/* WCAG 2.2 thresholds: 4.5 and 7 for body text, 3 for large text. */
const CHECKS = [
  { level: "AA", text: "Body", min: 4.5 },
  { level: "AAA", text: "Body", min: 7 },
  { level: "AA", text: "Large", min: 3 },
]

export function Pattern() {
  const [text, setText] = useState<string | null>("#1e3a8a")
  const [background, setBackground] = useState<string | null>("#dbeafe")
  const ratio = text && background ? getContrastRatio(text, background) : null

  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <CardTitle>Contrast checker</CardTitle>
        <CardDescription>
          WCAG ratios for a text and background pair.
        </CardDescription>
      </CardHeader>
      <CardContent>
        <div className="flex flex-col gap-5">
          <Frame>
            <FramePanel
              style={
                {
                  "--frame-panel-bg": background ?? undefined,
                  color: text ?? undefined,
                } as CSSProperties
              }
            >
              <div className="flex flex-col gap-1">
                <p className="text-lg font-semibold">
                  Quarterly report is ready
                </p>
                <p className="text-sm">
                  Revenue grew 18% on the quarter, led by self-serve plans.
                </p>
              </div>
            </FramePanel>
          </Frame>
          <div className="flex items-end gap-2">
            <Field className="min-w-0 flex-1">
              <FieldLabel id="contrast-text-label" htmlFor="contrast-text">
                Text
              </FieldLabel>
              <ColorPicker
                id="contrast-text"
                value={text}
                onValueChange={setText}
                aria-labelledby="contrast-text-label"
              />
            </Field>
            <Button
              variant="outline"
              size="icon"
              aria-label="Swap text and background"
              onClick={() => {
                setText(background)
                setBackground(text)
              }}
            >
              <IconPlaceholder
                lucide="ArrowLeftRightIcon"
                tabler="IconArrowsExchange"
                hugeicons="ArrowDataTransferHorizontalIcon"
                phosphor="ArrowsLeftRightIcon"
                remixicon="RiArrowLeftRightLine"
                aria-hidden="true"
              />
            </Button>
            <Field className="min-w-0 flex-1">
              <FieldLabel
                id="contrast-background-label"
                htmlFor="contrast-background"
              >
                Background
              </FieldLabel>
              <ColorPicker
                id="contrast-background"
                value={background}
                onValueChange={setBackground}
                aria-labelledby="contrast-background-label"
              />
            </Field>
          </div>
          <div className="flex items-center justify-between gap-4">
            <div role="status">
              <p className="text-3xl font-semibold tabular-nums">
                {ratio ? `${ratio.toFixed(2)}:1` : "None"}
              </p>
              <p className="text-muted-foreground text-xs">Contrast ratio</p>
            </div>
            <ul className="flex flex-col items-end gap-1.5">
              {CHECKS.map((check) => {
                const pass = ratio !== null && ratio >= check.min
                return (
                  <li
                    key={`${check.level}-${check.text}`}
                    className="flex items-center gap-2 text-xs"
                  >
                    <span className="text-muted-foreground">
                      {check.text} {check.level}
                    </span>
                    <Badge
                      variant={pass ? "success-light" : "destructive-light"}
                    >
                      {pass ? "Pass" : "Fail"}
                    </Badge>
                  </li>
                )
              })}
            </ul>
          </div>
        </div>
      </CardContent>
    </Card>
  )
}
```
