Skip to content
Brok UI

Loading…

No results

Check List

Open source

A bullet list whose marker is a check glyph — plan features, comparison points, what-you-get rows — from a server array or item children, with a plain or chip marker, a tone, three sizes and an excluded state.

Version
v1.1.0
Stability
stable
License
MIT
Related
Fact List
Badge
Icon
Todo Item
List Panel

Preview

  • Unlimited projects and collaborators
  • Design tokens for light and dark
  • Priority support with a one-day response
  • Export to Blade, Livewire or plain HTML
Tone
Size
previews.components.check-list.default.blade.php Blade
<x-ui.check-list class="max-w-sm" :items="[
    __('Unlimited projects and collaborators'),
    __('Design tokens for light and dark'),
    __('Priority support with a one-day response'),
    __('Export to Blade, Livewire or plain HTML'),
]" />

Tone options

Primary Current
Success Current
Muted Current
Destructive Current
Foreground Current

Size options

Sm Current
Md Current
Lg Current

Installation

terminal
php artisan ui:add check-list

Registry contract

php artisan ui:add check-list 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/check-list.blade.php
  • blade resources/views/components/ui/check-list/item.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.

check-list.md
# Brok UI: Check List (`check-list`)

A bullet list whose marker is a check glyph — plan features, comparison points, what-you-get rows — from a server array or item children, with a plain or chip marker, a tone, three sizes and an excluded state.

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

## Install

```bash
php artisan ui:add check-list
```

## Usage

```blade
<x-ui.check-list class="max-w-sm" :items="[
    __('Unlimited projects and collaborators'),
    __('Design tokens for light and dark'),
    __('Priority support with a one-day response'),
    __('Export to Blade, Livewire or plain HTML'),
]" />
```

## Props

- `items` (array, default `[]`) — Rows rendered on the server. A bare string is a row; an array row is `['label', 'included' => true, 'tone' => null, 'icon' => null, 'status' => null, 'statusLabel' => null]`. Combine with, or replace by, `<x-ui.check-list.item>` children in the default slot.
- `marker` ('plain'|'chip', default `plain`) — `plain` draws the bare glyph in the tone colour; `chip` sits it in a small tinted disc.
- `tone` ('primary'|'success'|'muted'|'destructive'|'foreground', default `primary`) — Marker colour by meaning. An item may override it with its own `tone`.
- `size` ('sm'|'md'|'lg', default `md`) — Type size and row gap: `sm` (text-sm, 8px), `md` (text-sm, 12px), `lg` (text-base, 16px).
- `icon` ('check'|'cross'|'dash'|'circle', default `check`) — The glyph every row draws unless it names its own. An excluded row swaps `check` for `dash` on its own.
- `layout` ('stack'|'row', default `stack`) — `stack` is one column; `row` lays the rows out as a wrapping line (trust points under a call to action, key results beside a case study).
- `included` (bool, default `true`) — Declared by @props in the registry Blade source.
- `status` (mixed|null, default `null`) — Declared by @props in the registry Blade source.
- `statusLabel` (mixed|null, default `null`) — Declared by @props in the registry Blade source.

## Use when

- Use to summarize, sequence, or present data so users can scan it quickly.
- A pricing tier, comparison column, hero or feature split lists what is included, one short point per row.
- A row must read as excluded (a feature the plan lacks) next to the included ones — pass `included => false`.

## Avoid when

- Do not add display-only ornament when the user needs actionable structure or exact comparison instead.
- The rows are label/value pairs — use `fact-list`.
- Each row is a task the visitor completes — use `todo-item` or `checkbox`.
- The list needs numbering or step semantics — use `stepper` or `timeline-steps`.

## Anti-patterns

- Adding display ornament without informational value

## Rules

- Use the `<brok:check-list>` tag (or `<x-ui.check-list>`) 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/check-list
- Registry JSON (files, props, contract): https://brokui.dev/r/open/check-list.json

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

Examples

chip.blade.php Blade
<x-ui.check-list marker="chip" class="max-w-sm">
    <x-ui.check-list.item>{{ __('Own the source — no runtime lock-in') }}</x-ui.check-list.item>
    <x-ui.check-list.item>{{ __('Keyboard support and focus states by default') }}</x-ui.check-list.item>
    <x-ui.check-list.item>{{ __('One token model for marketing and admin') }}</x-ui.check-list.item>
</x-ui.check-list>
excluded.blade.php Blade
<x-ui.check-list class="max-w-sm" :items="[
    __('Up to 5 team members'),
    __('10 GB of storage'),
    ['label' => __('Single sign-on'), 'included' => false],
    ['label' => __('Audit log'), 'included' => false],
]" />
long-content.blade.php Blade
<div class="max-w-xs">
    <x-ui.check-list marker="chip" :items="[
        __('A deliberately long point that verifies the text wraps under itself while the marker stays on the first line, without clipping or pushing the marker out of its column.'),
        __('https://example.com/a-very-long-url-that-has-no-natural-break-points-anywhere-in-it'),
        __('Short point'),
    ]" />
</div>
row.blade.php Blade
<x-ui.check-list layout="row" size="sm" class="max-w-lg font-medium text-muted-foreground" :items="[__('No credit card required'), __('Cancel anytime'), __('14-day free trial')]" />
sizes.blade.php Blade
<div class="flex flex-wrap gap-10">
    @foreach (['sm', 'md', 'lg'] as $size)
        <x-ui.check-list :size="$size" :items="[__('Size :size', ['size' => $size]), __('Two points per list'), __('Wraps at the column')]" class="w-48" />
    @endforeach
</div>
status.blade.php Blade
{{-- Result rows: `status` passed, failed or pending picks the glyph and the
     tone, and a screen reader hears the state word before each point, so
     the result is never only an icon or a colour. `statusLabel` changes the
     word, as the skipped row shows. --}}
<x-ui.check-list class="max-w-sm" marker="chip" :items="[
    ['label' => __('Every case has a run on this revision'), 'status' => 'passed'],
    ['label' => __('No must-not check failed'), 'status' => 'passed'],
    ['label' => __('Output stays under 400 words'), 'status' => 'failed'],
    ['label' => __('Second reviewer approved'), 'status' => 'pending'],
    ['label' => __('Load test'), 'status' => 'pending', 'statusLabel' => __('Skipped')],
]" />
tones.blade.php Blade
<div class="grid max-w-2xl gap-6 sm:grid-cols-2">
    <x-ui.check-list tone="success" marker="chip" :items="[__('Passed the accessibility audit'), __('Passed the contrast check')]" />
    <x-ui.check-list tone="muted" :items="[__('Optional: connect a custom domain'), __('Optional: invite your team')]" />
    <x-ui.check-list tone="destructive" icon="cross" marker="chip" :items="[__('No API access'), __('No audit log')]" />
    <x-ui.check-list tone="foreground" :items="[__('Ships with the starter kit'), __('Works offline')]" />
</div>

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
items array [] Rows rendered on the server. A bare string is a row; an array row is `['label', 'included' => true, 'tone' => null, 'icon' => null, 'status' => null, 'statusLabel' => null]`. Combine with, or replace by, `<x-ui.check-list.item>` children in the default slot.
marker 'plain' | 'chip' plain `plain` draws the bare glyph in the tone colour; `chip` sits it in a small tinted disc.
tone 'primary' | 'success' | 'muted' | 'destructive' | 'foreground' primary Marker colour by meaning. An item may override it with its own `tone`.
size 'sm' | 'md' | 'lg' md Type size and row gap: `sm` (text-sm, 8px), `md` (text-sm, 12px), `lg` (text-base, 16px).
icon 'check' | 'cross' | 'dash' | 'circle' check The glyph every row draws unless it names its own. An excluded row swaps `check` for `dash` on its own.
layout 'stack' | 'row' stack `stack` is one column; `row` lays the rows out as a wrapping line (trust points under a call to action, key results beside a case study).
included bool true Declared by @props in the registry Blade source.
status mixed | null null Declared by @props in the registry Blade source.
statusLabel mixed | null null Declared by @props in the registry Blade source.

Slots

  • default — `<x-ui.check-list.item>` children; each accepts `included`, `tone`, `icon`, `status` (passed, failed or pending: glyph, tone and a screen-reader state word) and `statusLabel` (replaces that word), and inherits `marker`/`size` from the list.
  • x-ui.check-list.item — Installed subcomponent from the registry item.

Data slots

Stable hooks for CSS overrides and browser tests.

check-list check-list-item check-list-marker check-list-state check-list-text

Behavior

  • Server rows and item children resolve marker, tone and size through the same `_styles.php` recipe, so a block that mixes the two cannot draw two treatments.
  • An excluded row (`included => false`) drops the text and marker to the muted tone and swaps the check for a dash; a `tone` or `icon` on that row wins over the default.
  • The text column is `min-w-0 flex-1`, so a long translated point wraps under itself and never pushes the marker.
  • A status row (passed, failed, pending) takes the matching glyph and tone and starts its text with a visually hidden state word (data-slot="check-list-state"); an excluded row starts with "Not included". Included rows without a status read their text only, as before.
  • Declares registry capability flags: a11y, authoredStateFixtures, responsive, rtl, darkMode, localized.

Guidance

Scannable data display

Present data for rapid scanning.

Use when

  • Use to summarize, sequence, or present data so users can scan it quickly.
  • A pricing tier, comparison column, hero or feature split lists what is included, one short point per row.
  • A row must read as excluded (a feature the plan lacks) next to the included ones — pass `included => false`.

Avoid when

  • Do not add display-only ornament when the user needs actionable structure or exact comparison instead.
  • The rows are label/value pairs — use `fact-list`.
  • Each row is a task the visitor completes — use `todo-item` or `checkbox`.
  • The list needs numbering or step semantics — use `stepper` or `timeline-steps`.

Use instead

  • Table for exact comparison
  • Plain text for a single value

Anti-patterns

  • Adding display ornament without informational value
Anatomy
check-list check-list-item check-list-marker check-list-text check-list-state
Theming hooks
check-list

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
  • Rendered as a real `<ul role="list">` of `<li>` rows so a screen reader announces the count and each point; the glyph is decorative (`aria-hidden`).
  • The excluded state is conveyed by the muted text and the dash glyph for sighted readers and by a visually hidden "Not included" before the text for screen readers, never by colour alone; `data-included="false"` is exposed for styling and tests.
  • A status row names its result in text (Passed, Failed, Pending, or statusLabel) before the point, because the marker glyph is aria-hidden and colour alone carries no meaning to assistive technology.
  • 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 files ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/check-list.blade.php Blade
{{--
    Check List — the bullet list whose marker is a check glyph: the features
    a plan includes, the points a comparison column makes, the "what you get"
    row under a hero. One `<ul>`, one marker treatment, one tone, instead of
    the five check paths and four icon treatments the blocks used to draw.

    Two sources, one markup:

      - `items` — a PHP array. A bare string is a row; an array row is
        `['label', 'included' => true, 'tone' => null, 'icon' => null,
        'status' => null, 'statusLabel' => null]`.
      - the default slot — `<x-ui.check-list.item>` children, for a row that
        carries more than a string (a badge, a tooltip, a translated label
        the block builds itself).

    `marker` sets the treatment: `plain` is the bare glyph in the tone colour,
    `chip` sits it in a small tinted disc. `tone` colours the marker by
    meaning (primary, success, muted, destructive). `size` scales the type
    and the row gap. `icon` picks the glyph (check, cross, dash); an excluded
    row (`included => false`) turns muted and swaps the check for a dash.
    A result row (`status` passed, failed or pending) takes its own glyph and
    tone; excluded and status rows say their state to screen readers.
    `layout="row"` lays the rows out as a wrapping line — the trust points
    under a call to action, the key results beside a case study.
--}}
@props([
    'items' => [],
    'marker' => 'plain',
    'tone' => 'primary',
    'size' => 'md',
    'icon' => 'check',
    'layout' => 'stack',
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $recipe = $styles['check-list'] ?? [];

    $marker = array_key_exists($marker, $recipe['markers'] ?? []) ? $marker : 'plain';
    $tone = array_key_exists($tone, $recipe['tones'] ?? []) ? $tone : 'primary';
    $size = array_key_exists($size, $recipe['sizes'] ?? []) ? $size : 'md';
    $layout = array_key_exists($layout, $recipe['layouts'] ?? []) ? $layout : 'stack';
    $listClass = trim(($recipe['layouts'][$layout] ?? '').' '.($recipe['sizes'][$size]['list'] ?? '').($layout === 'stack' ? ' '.($recipe['sizes'][$size]['stack'] ?? '') : ''));

    // Normalise the array shape once so the loop stays a plain readout: a bare
    // string is a row, an array row may carry its own tone/icon/included flag.
    $rows = collect((array) $items)
        ->map(fn ($entry): array => is_array($entry)
            ? [
                'label' => (string) ($entry['label'] ?? $entry['text'] ?? ''),
                'included' => (bool) ($entry['included'] ?? true),
                'tone' => $entry['tone'] ?? null,
                'icon' => $entry['icon'] ?? null,
                'status' => $entry['status'] ?? null,
                'statusLabel' => $entry['statusLabel'] ?? null,
            ]
            : ['label' => (string) $entry, 'included' => true, 'tone' => null, 'icon' => null, 'status' => null, 'statusLabel' => null])
        ->values();
@endphp

<ul
    data-slot="check-list"
    data-marker="{{ $marker }}"
    data-tone="{{ $tone }}"
    data-size="{{ $size }}"
    data-layout="{{ $layout }}"
    role="list"
    {{ $attributes->merge(['class' => $listClass]) }}
>
    @foreach ($rows as $row)
        <x-ui.check-list.item
            :marker="$marker"
            :tone="$row['tone'] ?? ($row['status'] !== null ? null : ($row['included'] ? $tone : 'muted'))"
            :size="$size"
            :icon="$row['icon'] ?? $icon"
            :included="$row['included']"
            :status="$row['status']"
            :status-label="$row['statusLabel']"
        >{{ $row['label'] }}</x-ui.check-list.item>
    @endforeach

    {{ $slot }}
</ul>
resources/views/components/ui/check-list/item.blade.php Blade
@aware([
    'marker' => 'plain',
    'tone' => 'primary',
    'size' => 'md',
    'icon' => 'check',
])

{{-- `marker`, `size` and `icon` are inherited from the list through @aware and
     may also be set per row, so they are declared as props too: a prop
     declaration is what keeps an explicitly passed value from being stripped
     off the aware variable. `tone` stays off the prop list on purpose — reading
     it from the bag tells an explicit row tone apart from the inherited one. --}}
@props([
    'included' => true,
    'marker' => 'plain',
    'size' => 'md',
    'icon' => 'check',
    // A result row: passed, failed or pending. Sets the glyph and tone
    // (check/success, cross/destructive, circle/muted) and a screen-reader
    // word before the text, so the state is never only an icon or a colour.
    'status' => null,
    // Replaces the screen-reader word of a status or excluded row.
    'statusLabel' => null,
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $recipe = $styles['check-list'] ?? [];

    $marker = array_key_exists((string) $marker, $recipe['markers'] ?? []) ? $marker : 'plain';
    $size = array_key_exists((string) $size, $recipe['sizes'] ?? []) ? $size : 'md';
    $icon = is_string($icon) && $icon !== '' ? $icon : 'check';
    $included = filter_var($included, FILTER_VALIDATE_BOOLEAN);

    // An excluded row reads as "not in this plan": muted marker, dash glyph,
    // muted text — unless the caller names a tone or glyph for it explicitly.
    $rowTone = $attributes->get('tone');
    $explicitTone = $rowTone !== null;
    $tone = $rowTone ?? ($tone ?? 'primary');
    $tone = array_key_exists((string) $tone, $recipe['tones'] ?? []) ? $tone : 'primary';
    $resolvedTone = $included || $explicitTone ? $tone : 'muted';
    $resolvedIcon = $included ? $icon : ($icon === 'check' ? 'dash' : $icon);

    // A status row picks its own glyph and tone unless the row names them.
    $statuses = [
        'passed' => ['icon' => 'check', 'tone' => 'success', 'label' => __('Passed')],
        'failed' => ['icon' => 'cross', 'tone' => 'destructive', 'label' => __('Failed')],
        'pending' => ['icon' => 'circle', 'tone' => 'muted', 'label' => __('Pending')],
    ];
    $status = is_string($status) && array_key_exists($status, $statuses) ? $status : null;
    if ($status !== null) {
        $resolvedTone = $explicitTone ? $tone : $statuses[$status]['tone'];
        $resolvedIcon = $icon !== 'check' ? $icon : $statuses[$status]['icon'];
    }

    // The words a screen reader hears before the text: the status, or "Not
    // included" on an excluded row, whose dash and muted colour are not read.
    $stateText = match (true) {
        filled($statusLabel) => (string) $statusLabel,
        $status !== null => $statuses[$status]['label'],
        ! $included => __('Not included'),
        default => null,
    };

    $glyphs = [
        'check' => '<path d="M20 6 9 17l-5-5" />',
        'cross' => '<path d="M18 6 6 18M6 6l12 12" />',
        'dash' => '<path d="M5 12h14" />',
        'circle' => '<circle cx="12" cy="12" r="8" />',
    ];
    $glyph = $glyphs[$resolvedIcon] ?? $glyphs['check'];

    $sizes = $recipe['sizes'][$size] ?? [];
    $toneClass = $recipe['tones'][$resolvedTone][$marker] ?? '';
    $markerClass = trim(($recipe['markers'][$marker] ?? '').' '.$toneClass.' '.($sizes[$marker === 'chip' ? 'chip-box' : 'glyph'] ?? ''));
    $svgClass = $marker === 'chip' ? ($sizes['chip-glyph'] ?? 'size-3') : 'size-full';
@endphp

<li
    data-slot="check-list-item"
    @if (! $included) data-included="false" @endif
    @if ($status !== null) data-status="{{ $status }}" @endif
    {{ $attributes->except('tone')->merge(['class' => trim(($recipe['item'] ?? '').' '.($sizes[$marker] ?? '').($included ? '' : ' '.($recipe['excluded'] ?? '')))]) }}
>
    <span data-slot="check-list-marker" class="{{ $markerClass }}" aria-hidden="true">
        <svg class="{{ $svgClass }}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">{!! $glyph !!}</svg>
    </span>
    <span data-slot="check-list-text" class="{{ $recipe['text'] ?? 'min-w-0 flex-1' }}">@if ($stateText !== null)<span data-slot="check-list-state" class="sr-only">{{ __(':state:', ['state' => $stateText]) }} </span>@endif{{ $slot }}</span>
</li>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.1.0
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