Skip to content
Brok UI

Loading…

No results

Card

Open source Core

A content container with header, content and footer sections; renders as div, a, article, section, li, details, dl or figure, with a frameless ghost variant.

Version
v1.4.0
Stability
stable
License
MIT
Related
Stack
Surface
Expandable Card

Preview

Billing

Manage your subscription and invoices.

Your plan renews on the 1st of each month.

previews.components.card.default.blade.php Blade
{{-- card.title is an h3 by default; `as` sets the level that fits the page outline (h2-h6, p or div). --}}
<x-ui.card class="w-full max-w-sm text-start">
    <x-ui.card.header>
        <x-ui.card.title as="h2">{{ __('Billing') }}</x-ui.card.title>
        <x-ui.card.description>{{ __('Manage your subscription and invoices.') }}</x-ui.card.description>
    </x-ui.card.header>
    <x-ui.card.content>
        <p class="text-sm text-muted-foreground">{{ __('Your plan renews on the 1st of each month.') }}</p>
    </x-ui.card.content>
    <x-ui.card.footer>
        <x-ui.button class="w-full">{{ __('Update plan') }}</x-ui.button>
    </x-ui.card.footer>
</x-ui.card>

Installation

terminal
php artisan ui:add card

Registry contract

php artisan ui:add card 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/card.blade.php
  • blade resources/views/components/ui/card/header.blade.php
  • blade resources/views/components/ui/card/title.blade.php
  • blade resources/views/components/ui/card/description.blade.php
  • blade resources/views/components/ui/card/content.blade.php
  • blade resources/views/components/ui/card/footer.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.

card.md
# Brok UI: Card (`card`)

A content container with header, content and footer sections; renders as div, a, article, section, li, details, dl or figure, with a frameless ghost variant.

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

## Install

```bash
php artisan ui:add card
```

## Usage

```blade
{{-- card.title is an h3 by default; `as` sets the level that fits the page outline (h2-h6, p or div). --}}
<x-ui.card class="w-full max-w-sm text-start">
    <x-ui.card.header>
        <x-ui.card.title as="h2">{{ __('Billing') }}</x-ui.card.title>
        <x-ui.card.description>{{ __('Manage your subscription and invoices.') }}</x-ui.card.description>
    </x-ui.card.header>
    <x-ui.card.content>
        <p class="text-sm text-muted-foreground">{{ __('Your plan renews on the 1st of each month.') }}</p>
    </x-ui.card.content>
    <x-ui.card.footer>
        <x-ui.button class="w-full">{{ __('Update plan') }}</x-ui.button>
    </x-ui.card.footer>
</x-ui.card>
```

## Props

- `as` (string, default `div`) — Element the card renders as: div, article, section, li, details, dl, or figure; ignored (forced to a) when href is set.
- `href` (mixed|null, default `null`) — Renders the card as a link and adds a hover lift, while keeping the same surface recipe.
- `variant` (string, default `default`) — default is the bordered, filled surface; ghost has no frame, fill, or shadow, so only the layout parts sit on the page background.

## Use when

- Use to group related content into scannable units for browsing, prioritization, or rearrangement.
- Grouping related content (a title, description, body, and actions) into one bordered, elevated surface.
- Building a clickable card that links to a whole record or page via href.

## Avoid when

- Do not use card-like structures when dense row-by-row comparison would be clearer in a list or table.
- The content needs no visual framing, just spacing; use stack or a plain surface instead.
- The composition wants layout parts without a bordered surface at all; use variant="ghost" rather than reaching for a different primitive.

## Anti-patterns

- Turning every object into a card

## Rules

- Use the `<brok:card>` tag (or `<x-ui.card>`) 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/card
- Registry JSON (files, props, contract): https://brokui.dev/r/open/card.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
as string div Element the card renders as: div, article, section, li, details, dl, or figure; ignored (forced to a) when href is set.
href mixed | null null Renders the card as a link and adds a hover lift, while keeping the same surface recipe.
variant string default default is the bordered, filled surface; ghost has no frame, fill, or shadow, so only the layout parts sit on the page background.

Slots

  • default — Compositional parts: header (with title and description), content, and footer.
  • x-ui.card.header — Installed subcomponent from the registry item.
  • x-ui.card.title — Installed subcomponent from the registry item.
  • x-ui.card.description — Installed subcomponent from the registry item.
  • x-ui.card.content — Installed subcomponent from the registry item.
  • x-ui.card.footer — Installed subcomponent from the registry item.

Renders as div, a, article, section, li, details, dl, figure.

Data slots

Stable hooks for CSS overrides and browser tests.

card-content card-description card-footer card-header card-title {{ $slotName }}

Behavior

  • A card with href renders as a real anchor and gains a hover lift, so the whole surface is clickable and keyboard-focusable.
  • Components built on top of the card (such as current-plan or an approval workflow) can override data-slot and data-variant to present under their own name while reusing the same recipe.
  • Structural primitive only; all behavior comes from composed children.
  • Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.

Guidance

Card-based organization

Organize browsable objects into scannable units.

Use when

  • Use to group related content into scannable units for browsing, prioritization, or rearrangement.
  • Grouping related content (a title, description, body, and actions) into one bordered, elevated surface.
  • Building a clickable card that links to a whole record or page via href.

Avoid when

  • Do not use card-like structures when dense row-by-row comparison would be clearer in a list or table.
  • The content needs no visual framing, just spacing; use stack or a plain surface instead.
  • The composition wants layout parts without a bordered surface at all; use variant="ghost" rather than reaching for a different primitive.

Use instead

  • Table for exact comparison
  • List for compact scanning

Anti-patterns

  • Turning every object into a card
Anatomy
root card-content card-description card-footer card-header card-title
Theming hooks
card

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
  • A link card is a real anchor, not a div with a click handler, so it is reachable and operable from the keyboard like any other link.
  • card.title is an h3 by default; set as to the level that follows the heading above the card so the outline has no skipped or out-of-order levels, or to p/div when the title should not be a heading at all.
  • 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/card.blade.php Blade
@props([
    // Element the card renders as: div (default), a (with href), article,
    // section, figure. A link card keeps the surface recipe and gains the
    // hover lift.
    'as' => 'div',
    'href' => null,
    // default: the bordered surface. ghost: no frame, no fill, no shadow —
    // the card's layout parts on the page background, for a composition
    // that sometimes wants the surface and sometimes does not.
    'variant' => 'default',
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $allowedTags = ['div', 'a', 'article', 'section', 'li', 'details', 'dl', 'figure'];
    $tag = $href !== null ? 'a' : (in_array($as, $allowedTags, true) ? $as : 'div');
    $variant = $styles['normalizeVariant']($variant);
    $variant = $variant === 'ghost' ? 'ghost' : 'default';
    $baseClass = $variant === 'ghost' ? $styles['card']['ghost'] : $styles['card']['base'];
    // A max-width passed by the caller replaces the base `max-w-full` rather than
    // competing with it: Tailwind orders `max-w-2xl`, `max-w-7xl` and arbitrary
    // values before `max-w-full`, so the base would otherwise keep the card full width.
    if (preg_match('/(?<!\S)max-w-\S+/', (string) $attributes->get('class', ''))) {
        $baseClass = trim((string) preg_replace('/(?<!\S)max-w-full(?!\S)/', '', $baseClass));
    }
    $classes = $baseClass.($tag === 'a' ? ' '.$styles['card']['link'] : '');
    // A component built on this card (current-plan, approval-workflow, …)
    // passes its own data-slot; the browser keeps only the first of two
    // identical attributes, so read the override and print exactly one.
    $slotName = (string) ($attributes->get('data-slot') ?: 'card');
    // Same rule for data-variant: a composing component's own variant name
    // wins over the card's `ghost` marker.
    $variantName = (string) ($attributes->get('data-variant') ?: ($variant === 'ghost' ? 'ghost' : ''));
@endphp

<{{ $tag }}
    data-slot="{{ $slotName }}"
    @if ($variantName !== '') data-variant="{{ $variantName }}" @endif
    @if ($tag === 'a') href="{{ $href }}" @endif
    {{ $attributes->except(['data-slot', 'data-variant'])->merge(['class' => $classes]) }}
>
    {{ $slot }}
</{{ $tag }}>
resources/views/components/ui/card/header.blade.php Blade
<div
    data-slot="card-header"
    {{ $attributes->merge(['class' => 'flex min-w-0 flex-col gap-stack-sm p-card']) }}
>
    {{ $slot }}
</div>
resources/views/components/ui/card/title.blade.php Blade
@props([
    // Element the title renders as: h3 (default), h2, h4, h5, h6, p or div.
    // Match the page's heading outline; an unknown value falls back to h3.
    'as' => 'h3',
])

@php
    $tag = in_array($as, ['h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div'], true) ? $as : 'h3';
@endphp

<{{ $tag }}
    data-slot="card-title"
    {{ $attributes->merge(['class' => 'break-words text-lg font-semibold leading-none tracking-tight']) }}
>
    {{ $slot }}
</{{ $tag }}>
resources/views/components/ui/card/description.blade.php Blade
<p
    data-slot="card-description"
    {{ $attributes->merge(['class' => 'break-words text-sm text-muted-foreground']) }}
>
    {{ $slot }}
</p>
resources/views/components/ui/card/content.blade.php Blade
<div
    data-slot="card-content"
    {{ $attributes->merge(['class' => 'min-w-0 p-card pt-0']) }}
>
    {{ $slot }}
</div>
resources/views/components/ui/card/footer.blade.php Blade
<div
    data-slot="card-footer"
    {{ $attributes->merge(['class' => 'flex min-w-0 items-center p-card pt-0']) }}
>
    {{ $slot }}
</div>

Ownership & lifecycle

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