Skip to content
Brok UI

Loading…

No results

Color Contrast

Open source

Reports the WCAG 2.0 contrast ratio between a base and background colour with an AA/AAA/Fail grade — luminance and ratio computed from hex inputs, or overridden for APCA/pre-computed values.

Version
v1.1.4
Stability
stable
License
MIT
Related
Color Palette
Color Swatch Selector
Color Dropdown

Preview

Text / Surface 17.85:1 AAA On primary 4.47:1 Fail Muted 2.45:1 Fail
17.85:1 2.28:1 1.20:1
Variant
previews.components.color-contrast.default.blade.php Blade
<div class="flex flex-col items-start gap-4">
    <div class="flex flex-wrap items-center gap-3">
        <x-ui.color-contrast base="#0f172a" against="#ffffff" label="Text / Surface" />
        <x-ui.color-contrast base="#ffffff" against="#6366f1" label="On primary" />
        <x-ui.color-contrast base="#94a3b8" against="#f8fafc" label="Muted" />
    </div>

    <div class="flex flex-wrap items-center gap-3">
        <x-ui.color-contrast variant="icon" base="#0f172a" against="#ffffff" />
        <x-ui.color-contrast variant="icon" base="#ffffff" against="#22c55e" />
        <x-ui.color-contrast variant="icon" base="#cbd5e1" against="#e2e8f0" />
    </div>
</div>
Pill Current
Icon Current

Installation

terminal
php artisan ui:add color-contrast

Registry contract

php artisan ui:add color-contrast writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/color-contrast.blade.php
Registry dependencies
None — installs on its own.
Packages
composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

color-contrast.md
# Brok UI: Color Contrast (`color-contrast`)

Reports the WCAG 2.0 contrast ratio between a base and background colour with an AA/AAA/Fail grade — luminance and ratio computed from hex inputs, or overridden for APCA/pre-computed values.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add color-contrast
```

## Usage

```blade
<div class="flex flex-col items-start gap-4">
    <div class="flex flex-wrap items-center gap-3">
        <x-ui.color-contrast base="#0f172a" against="#ffffff" label="Text / Surface" />
        <x-ui.color-contrast base="#ffffff" against="#6366f1" label="On primary" />
        <x-ui.color-contrast base="#94a3b8" against="#f8fafc" label="Muted" />
    </div>

    <div class="flex flex-wrap items-center gap-3">
        <x-ui.color-contrast variant="icon" base="#0f172a" against="#ffffff" />
        <x-ui.color-contrast variant="icon" base="#ffffff" against="#22c55e" />
        <x-ui.color-contrast variant="icon" base="#cbd5e1" against="#e2e8f0" />
    </div>
</div>
```

## Props

- `base` (mixed|null, default `null`) — Foreground/text colour as a hex string.
- `against` (mixed|null, default `null`) — Background colour it is compared against, as a hex string.
- `ratio` (mixed|null, default `null`) — Explicit contrast ratio override, used instead of computing one from base/against (e.g. for APCA).
- `passes` (mixed|null, default `null`) — Explicit pass/fail or grade override ("AA", "AAA", "fail" or a boolean) instead of deriving it from the ratio.
- `label` (mixed|null, default `null`) — Optional text shown before the ratio, such as a token pairing name.
- `variant` (pill|icon, default `pill`) — pill renders a text grade badge; icon renders a compact pass/fail glyph.

## Use when

- Use to document, inspect, and apply design-system decisions consistently across teams and AI tooling.
- Reporting whether two colours meet a WCAG contrast threshold, such as validating a token pairing in a style guide or design QA.
- Showing a live AA/AAA/Fail grade next to a colour combination without asking the reader to compute the ratio themselves.

## Avoid when

- Do not present token-level controls to end users who only need product functionality.
- Swatching a single colour token on its own; use color-token, which does not compare against a background.

## Anti-patterns

- Exposing implementation tokens as product settings

## Rules

- Use the `<brok:color-contrast>` tag (or `<x-ui.color-contrast>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/color-contrast
- Registry JSON (files, props, contract): https://brokui.dev/r/open/color-contrast.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
base mixed | null null Foreground/text colour as a hex string.
against mixed | null null Background colour it is compared against, as a hex string.
ratio mixed | null null Explicit contrast ratio override, used instead of computing one from base/against (e.g. for APCA).
passes mixed | null null Explicit pass/fail or grade override ("AA", "AAA", "fail" or a boolean) instead of deriving it from the ratio.
label mixed | null null Optional text shown before the ratio, such as a token pairing name.
variant pill | icon pill pill renders a text grade badge; icon renders a compact pass/fail glyph.

Slots

  • default — Not used by this component; present for attribute-bag compatibility.

Data slots

Stable hooks for CSS overrides and browser tests.

color-contrast color-contrast-badge color-contrast-ratio color-contrast-swatch

Behavior

  • Computes relative luminance and the WCAG 2.0 ratio from base/against hex colours, or uses an explicit ratio/passes override for APCA or pre-computed values.
  • Grades the result AAA (>= 7), AA (>= 4.5) or Fail, and renders either a text pill or a compact pass/fail icon depending on variant.
  • Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.

Guidance

Design-token documentation

Inspect and govern design-system decisions.

Use when

  • Use to document, inspect, and apply design-system decisions consistently across teams and AI tooling.
  • Reporting whether two colours meet a WCAG contrast threshold, such as validating a token pairing in a style guide or design QA.
  • Showing a live AA/AAA/Fail grade next to a colour combination without asking the reader to compute the ratio themselves.

Avoid when

  • Do not present token-level controls to end users who only need product functionality.
  • Swatching a single colour token on its own; use color-token, which does not compare against a background.

Use instead

  • Product-level controls for end users

Anti-patterns

  • Exposing implementation tokens as product settings
Anatomy
root color-contrast-swatch color-contrast-ratio color-contrast-badge
Theming hooks
color-contrast color-contrast-badge color-contrast-ratio color-contrast-swatch

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
No component-owned keyboard interaction; native element behavior applies.
Focus
none
  • The whole control exposes its computed ratio and grade through a single role="img" aria-label, so screen readers get one summarised announcement instead of reading each part separately.
  • The swatch's inner glyph and the icon variant's pass/fail glyph are decorative and hidden with aria-hidden.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Safe

Livewire can update this component through forwarded wire:* attributes.

Source

The exact, editable file ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/color-contrast.blade.php Blade
@props([
    'base' => null,
    'against' => null,
    'ratio' => null,
    'passes' => null,
    'label' => null,
    'variant' => 'pill',
])

{{--
    Color Contrast (tokenui.dev-inspired) — reports the WCAG 2.0 contrast ratio
    between a base (text) colour and the colour it sits `against` (background).
    Relative luminance and the ratio are computed from hex inputs in PHP; pass an
    explicit `ratio`/`passes` to override (e.g. for APCA or pre-computed values).
    `variant="icon"` renders a compact pass/fail glyph instead of a grade pill.
--}}
@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $variant = $styles['normalizeVariant']($variant);
    $parseHex = static function (?string $hex): ?array {
        $hex = ltrim(trim((string) $hex), '#');
        if (strlen($hex) === 3) {
            $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
        }
        if (! preg_match('/^[0-9a-fA-F]{6}$/', $hex)) {
            return null;
        }

        return [hexdec(substr($hex, 0, 2)), hexdec(substr($hex, 2, 2)), hexdec(substr($hex, 4, 2))];
    };

    $luminance = static function (array $rgb): float {
        $linear = array_map(static function ($c) {
            $c /= 255;

            return $c <= 0.03928 ? $c / 12.92 : (($c + 0.055) / 1.055) ** 2.4;
        }, $rgb);

        return 0.2126 * $linear[0] + 0.7152 * $linear[1] + 0.0722 * $linear[2];
    };

    $baseRgb = $parseHex($base);
    $againstRgb = $parseHex($against);

    $computed = null;
    if ($ratio !== null && $ratio !== '') {
        $computed = (float) $ratio;
    } elseif ($baseRgb && $againstRgb) {
        $l1 = $luminance($baseRgb);
        $l2 = $luminance($againstRgb);
        $computed = (max($l1, $l2) + 0.05) / (min($l1, $l2) + 0.05);
    }

    // Resolve the grade: explicit override wins, otherwise derive from the ratio.
    $grade = $passes;
    if (is_string($grade)) {
        $grade = strtolower($grade) === 'fail' ? 'fail' : strtoupper($grade);
    }
    if (($grade === null || $grade === '') && $computed !== null) {
        $grade = $computed >= 7 ? 'AAA' : ($computed >= 4.5 ? 'AA' : 'fail');
    }

    $isFail = $grade === 'fail' || $grade === false;
    $isPass = ! $isFail && $grade !== null && $grade !== '';
    $gradeLabel = match (true) {
        $grade === 'AAA' => 'AAA',
        $grade === 'AA' => 'AA',
        $grade === true => __('Pass'),
        $isFail => __('Fail'),
        default => '—',
    };

    $ratioText = $computed !== null ? number_format($computed, 2).':1' : '—';
    $variant = in_array($variant, ['pill', 'icon'], true) ? $variant : 'pill';
@endphp

<span
    data-slot="color-contrast"
    role="img"
    aria-label="{{ __('Contrast :ratio :grade', ['ratio' => $ratioText, 'grade' => $isPass ? $gradeLabel : __('Fail')]) }}"
    {{ $attributes->merge(['class' => 'inline-flex items-center gap-2 rounded-md border border-border bg-card px-2 py-1 align-middle']) }}
>
    @if ($baseRgb && $againstRgb)
        {{-- Swatch colours are arbitrary user data (any CSS colour), never palette utilities. --}}
        <span
            data-slot="color-contrast-swatch"
            class="inline-flex size-6 shrink-0 items-center justify-center overflow-hidden rounded-md border border-border"
            aria-hidden="true"
            style="background-color: {{ $against }}"
        >
            <span class="font-mono text-xs font-semibold leading-none" style="color: {{ $base }}">Aa</span>
        </span>
    @endif

    @if ($label)
        <span class="text-xs font-medium text-foreground">{{ $label }}</span>
    @endif

    <span data-slot="color-contrast-ratio" class="font-mono text-xs text-muted-foreground">{{ $ratioText }}</span>

    @if ($variant === 'icon')
        <span
            data-slot="color-contrast-badge"
            @class([
                'inline-flex size-4 shrink-0 items-center justify-center rounded-full',
                'bg-success/15 text-success' => $isPass,
                'bg-destructive/15 text-destructive' => $isFail,
                'bg-muted text-muted-foreground' => ! $isPass && ! $isFail,
            ])
        >
            @if ($isPass)
                <svg xmlns="http://www.w3.org/2000/svg" class="size-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
            @elseif ($isFail)
                <svg xmlns="http://www.w3.org/2000/svg" class="size-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
            @else
                <span class="text-2xs font-semibold">?</span>
            @endif
        </span>
    @else
        <span
            data-slot="color-contrast-badge"
            @class([
                'inline-flex items-center rounded-full px-2 py-0.5 text-2xs font-semibold uppercase tracking-wide',
                'bg-success/15 text-success' => $isPass,
                'bg-destructive/15 text-destructive' => $isFail,
                'bg-muted text-muted-foreground' => ! $isPass && ! $isFail,
            ])
        >{{ $gradeLabel }}</span>
    @endif
</span>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.1.4
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0