Empty
Empty state for when there is no data to display.
Preview
<x-ui.empty>
<x-ui.empty.header>
<x-ui.empty.media variant="icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
</svg>
</x-ui.empty.media>
<x-ui.empty.title>{{ __('No projects yet') }}</x-ui.empty.title>
<x-ui.empty.description>{{ __('Create your first project to get started.') }}</x-ui.empty.description>
</x-ui.empty.header>
<x-ui.empty.content>
<x-ui.button>{{ __('Create project') }}</x-ui.button>
</x-ui.empty.content>
</x-ui.empty>
Installation
php artisan ui:add empty
Registry contract
php artisan ui:add empty
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.
-
resources/views/components/ui/empty.blade.php -
resources/views/components/ui/empty/header.blade.php -
resources/views/components/ui/empty/media.blade.php -
resources/views/components/ui/empty/title.blade.php -
resources/views/components/ui/empty/description.blade.php -
resources/views/components/ui/empty/content.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.
# Brok UI: Empty (`empty`)
Empty state for when there is no data to display.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add empty
```
## Usage
```blade
<x-ui.empty>
<x-ui.empty.header>
<x-ui.empty.media variant="icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
</svg>
</x-ui.empty.media>
<x-ui.empty.title>{{ __('No projects yet') }}</x-ui.empty.title>
<x-ui.empty.description>{{ __('Create your first project to get started.') }}</x-ui.empty.description>
</x-ui.empty.header>
<x-ui.empty.content>
<x-ui.button>{{ __('Create project') }}</x-ui.button>
</x-ui.empty.content>
</x-ui.empty>
```
## Props
- `variant` (default|outline|plain|icon|destructive|quiet, default `default', 'size' => 'default', 'placement' => 'center`) — Container style: default (dashed border), outline (solid border and card surface), or plain (no border). On empty.media: default (bare), icon (muted tile), outline, destructive, or quiet (no tile, only the icon in a softened muted tone at 24, 32 or 40px for sm, default or lg).
- `as` (string, default `div`) — Declared by @props in the registry Blade source.
## Use when
- Use to explain a blank state and show the next action.
- Replacing a list, table, or search result with a centered message and icon when there is no data to show.
- Needing consistent header, media, title, description, and content sub-components for empty, no-results, and error states.
## Avoid when
- Do not leave an empty screen without orientation or a recovery path.
- Always explain what belongs here and what the user should do next.
- Showing a full-page loading placeholder before data arrives; use skeleton instead.
- Announcing a transient status change; use status or a toast instead.
## Anti-patterns
- Showing a blank surface without explanation or action
## Rules
- Use the `<brok:empty>` tag (or `<x-ui.empty>`) 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/empty
- Registry JSON (files, props, contract): https://brokui.dev/r/open/empty.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Examples
<x-ui.empty variant="outline">
<x-ui.empty.header>
<x-ui.empty.media variant="icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
</svg>
</x-ui.empty.media>
<x-ui.empty.title as="h3">{{ __('No projects yet') }}</x-ui.empty.title>
<x-ui.empty.description>{{ __('Get started by creating your first project, or import an existing one.') }}</x-ui.empty.description>
</x-ui.empty.header>
<x-ui.empty.content>
<div class="flex flex-col items-center justify-center gap-2 sm:flex-row">
<x-ui.button>{{ __('New project') }}</x-ui.button>
<x-ui.button variant="outline">{{ __('Import existing') }}</x-ui.button>
</div>
</x-ui.empty.content>
</x-ui.empty>
<x-ui.empty variant="outline">
<x-ui.empty.header>
<x-ui.empty.media variant="destructive">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
</x-ui.empty.media>
<x-ui.empty.title as="h3">{{ __('Something went wrong') }}</x-ui.empty.title>
<x-ui.empty.description>{{ __('We could not load this data. Check your connection and try again.') }}</x-ui.empty.description>
</x-ui.empty.header>
<x-ui.empty.content>
<x-ui.button variant="outline">{{ __('Retry') }}</x-ui.button>
</x-ui.empty.content>
</x-ui.empty>
{{-- Linear style: a plain root in a tall list area, the content set optically above the middle,
a quiet icon without a tile, one sentence and one outline action with its shortcut. --}}
<div class="flex min-h-96 w-full flex-col">
<x-ui.empty variant="plain" placement="upper">
<x-ui.empty.header>
<x-ui.empty.media variant="quiet" size="lg">
<x-ui.icon name="inbox" :stroke-width="1.5" />
</x-ui.empty.media>
<x-ui.empty.title as="h2">{{ __('No issues') }}</x-ui.empty.title>
<x-ui.empty.description>{{ __('Issues assigned to you will show up here.') }}</x-ui.empty.description>
</x-ui.empty.header>
<x-ui.empty.content>
<x-ui.button variant="outline" size="sm" aria-keyshortcuts="C">
{{ __('Create issue') }}
<x-ui.kbd aria-hidden="true">C</x-ui.kbd>
</x-ui.button>
</x-ui.empty.content>
</x-ui.empty>
</div>
No Results
<x-ui.empty>
<x-ui.empty.header>
<x-ui.empty.media variant="icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
</svg>
</x-ui.empty.media>
<x-ui.empty.title as="h3">{{ __('No results found') }}</x-ui.empty.title>
<x-ui.empty.description>{{ __('No matches for your search. Try a different term or clear the filters.') }}</x-ui.empty.description>
</x-ui.empty.header>
<x-ui.empty.content>
<x-ui.button variant="outline">{{ __('Clear filters') }}</x-ui.button>
</x-ui.empty.content>
</x-ui.empty>
<x-ui.empty variant="outline">
<x-ui.empty.header>
<x-ui.empty.media variant="icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M18 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0ZM3 19.235v-.11a6.375 6.375 0 0 1 12.75 0v.109A12.318 12.318 0 0 1 9.374 21c-2.331 0-4.512-.645-6.374-1.766Z" />
</svg>
</x-ui.empty.media>
<x-ui.empty.title as="h3">{{ __('No team members') }}</x-ui.empty.title>
<x-ui.empty.description>{{ __('Invite people to collaborate on this workspace.') }}</x-ui.empty.description>
</x-ui.empty.header>
<x-ui.empty.content>
<x-ui.button>{{ __('Invite people') }}</x-ui.button>
</x-ui.empty.content>
</x-ui.empty>
<x-ui.empty variant="plain" size="lg">
<x-ui.empty.header>
<x-ui.empty.media variant="icon" size="lg">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5m6 4.125 2.25 2.25m0 0 2.25 2.25M12 13.875l2.25-2.25M12 13.875l-2.25 2.25M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z" />
</svg>
</x-ui.empty.media>
<x-ui.empty.title as="h3">{{ __('No data to display') }}</x-ui.empty.title>
<x-ui.empty.description>{{ __('Once records are added they will appear here.') }}</x-ui.empty.description>
</x-ui.empty.header>
</x-ui.empty>
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | default | outline | plain | icon | destructive | quiet | default', 'size' => 'default', 'placement' => 'c... | Container style: default (dashed border), outline (solid border and card surface), or plain (no border). On empty.media: default (bare), icon (muted tile), outline, destructive, or quiet (no tile, only the icon in a softened muted tone at 24, 32 or 40px for sm, default or lg). |
| as | string | div | Declared by @props in the registry Blade source. |
Slots
default— Empty state content, typically the header, media, title, description, and content sub-components.x-ui.empty.header— Installed subcomponent from the registry item.x-ui.empty.media— Installed subcomponent from the registry item.x-ui.empty.title— Installed subcomponent from the registry item.x-ui.empty.description— Installed subcomponent from the registry item.x-ui.empty.content— Installed subcomponent from the registry item.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- Composes optional sub-components (empty-media, empty-header, empty-title, empty-description, empty-content); each renders only when used.
- Title and description text wraps with text-balance so the message stays readable at narrow widths.
- placement="upper" shares the free height between two empty flex spacers at 2:3, so in a tall or flex-1 container the content starts at about 40% from the top; without free height it sits at the top like center.
- Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.
Guidance
Explain the absence of content and provide a next step.
Use when
- Use to explain a blank state and show the next action.
- Replacing a list, table, or search result with a centered message and icon when there is no data to show.
- Needing consistent header, media, title, description, and content sub-components for empty, no-results, and error states.
Avoid when
- Do not leave an empty screen without orientation or a recovery path.
- Always explain what belongs here and what the user should do next.
- Showing a full-page loading placeholder before data arrives; use skeleton instead.
- Announcing a transient status change; use status or a toast instead.
Use instead
- Inline status for temporary absence
Anti-patterns
- Showing a blank surface without explanation or action
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- No component-owned keyboard interaction; native element behavior applies.
- Focus
none
- empty-media is decorative and hidden from assistive tech with aria-hidden.
- Render empty-title with a heading tag (as="h2") when the empty state replaces page content that would otherwise carry a heading.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, 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 underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
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.
@props(['variant' => 'default', 'size' => 'default', 'placement' => 'center'])
@php
$styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
$variant = $styles['normalizeVariant']($variant);
$variants = [
'default' => 'border border-dashed border-border bg-transparent',
'outline' => 'border border-border bg-card',
'plain' => 'border-0 bg-transparent',
];
$sizes = [
'sm' => 'gap-4 p-4 md:p-8',
// `md` is the house default-size token; `default` is kept as an alias so
// existing `size="default"` usage stays valid (shared map value).
'md' => 'gap-6 p-6 md:p-12',
'default' => 'gap-6 p-6 md:p-12',
'lg' => 'gap-8 p-10 md:p-16',
];
// `upper` sits the content optically above the middle: two empty flex
// spacers share the free height 2:3, so the content starts at about 40%.
$placements = [
'center' => 'justify-center',
'upper' => "justify-start before:grow-2 before:content-[''] after:grow-3 after:content-['']",
];
$placement = array_key_exists($placement, $placements) ? $placement : 'center';
$classes = 'flex min-w-0 flex-1 flex-col items-center rounded-lg text-center text-balance '
. $placements[$placement] . ' '
. ($variants[$variant] ?? $variants['default']) . ' '
. ($sizes[$size] ?? $sizes['default']);
@endphp
<div data-slot="empty" data-variant="{{ $variant }}" data-size="{{ $size }}" data-placement="{{ $placement }}" {{ $attributes->merge(['class' => $classes]) }}>{{ $slot }}</div>
<div data-slot="empty-header" {{ $attributes->merge(['class' => 'flex max-w-sm flex-col items-center gap-2 text-center']) }}>{{ $slot }}</div>
@props(['variant' => 'default', 'size' => 'default'])
@php
$styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
$variant = $styles['normalizeVariant']($variant);
$sizes = [
'sm' => 'size-8 [&_svg]:size-5',
'default' => 'size-10 [&_svg]:size-6',
'lg' => 'size-12 [&_svg]:size-7',
];
$shell = 'flex shrink-0 items-center justify-center rounded-lg ' . ($sizes[$size] ?? $sizes['default']);
$variants = [
'default' => 'bg-transparent',
'icon' => $shell . ' bg-muted text-foreground',
'outline' => $shell . ' border border-border bg-background text-foreground',
'destructive' => $shell . ' bg-destructive/10 text-destructive',
];
// `quiet`: no tile, only the icon in a softened muted tone at 24/32/40px.
// The stroke comes from the icon itself.
$quietSizes = [
'sm' => '[&_svg]:size-6',
'default' => '[&_svg]:size-8',
'lg' => '[&_svg]:size-10',
];
$variants['quiet'] = 'flex shrink-0 items-center justify-center text-muted-foreground/70 ' . ($quietSizes[$size] ?? $quietSizes['default']);
$classes = 'mb-2 ' . ($variants[$variant] ?? $variants['default']);
if ($variant === 'default') {
$classes = 'flex shrink-0 items-center justify-center mb-2 bg-transparent';
}
@endphp
<div data-slot="empty-media" data-variant="{{ $variant }}" data-size="{{ $size }}" aria-hidden="true" {{ $attributes->merge(['class' => $classes]) }}>{{ $slot }}</div>
@props(['as' => 'div'])
@php
$allowedTags = ['div', 'p', 'h2', 'h3', 'h4', 'h5', 'h6'];
$tag = in_array($as, $allowedTags, true) ? $as : 'div';
@endphp
<{{ $tag }} data-slot="empty-title" {{ $attributes->merge(['class' => 'text-lg font-medium tracking-tight text-foreground']) }}>{{ $slot }}</{{ $tag }}>
<div data-slot="empty-description" {{ $attributes->merge(['class' => 'text-sm/relaxed text-muted-foreground']) }}>{{ $slot }}</div>
<div data-slot="empty-content" {{ $attributes->merge(['class' => 'flex w-full max-w-sm min-w-0 flex-col items-center gap-2 text-sm']) }}>{{ $slot }}</div>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.5.3 - 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