Skip to content
Brok UI

Loading…

No results

Empty

Open source

Empty state for when there is no data to display.

Version
v1.5.3
Stability
stable
License
MIT
Related
Status
Skeleton

Preview

No projects yet
Create your first project to get started.
Variant
previews.components.empty.default.blade.php 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>
Default', 'size' => 'default', 'placement' => 'center Current
Default Current
Outline Current
Plain Current

Installation

terminal
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.

  • blade resources/views/components/ui/empty.blade.php
  • blade resources/views/components/ui/empty/header.blade.php
  • blade resources/views/components/ui/empty/media.blade.php
  • blade resources/views/components/ui/empty/title.blade.php
  • blade resources/views/components/ui/empty/description.blade.php
  • blade 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.

empty.md
# 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

actions.blade.php Blade
<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>
error.blade.php Blade
<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.blade.php Blade
{{-- 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.blade.php 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">
                <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>
outline.blade.php Blade
<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>
plain.blade.php Blade
<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

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
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.

empty empty-content empty-description empty-header empty-media empty-title

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

Empty state

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
root empty-content empty-description empty-header empty-media empty-title
Theming hooks
empty empty-content empty-description empty-header empty-media empty-title

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
  • 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-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/empty.blade.php Blade
@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>
resources/views/components/ui/empty/header.blade.php Blade
<div data-slot="empty-header" {{ $attributes->merge(['class' => 'flex max-w-sm flex-col items-center gap-2 text-center']) }}>{{ $slot }}</div>
resources/views/components/ui/empty/media.blade.php Blade
@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>
resources/views/components/ui/empty/title.blade.php Blade
@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 }}>
resources/views/components/ui/empty/description.blade.php Blade
<div data-slot="empty-description" {{ $attributes->merge(['class' => 'text-sm/relaxed text-muted-foreground']) }}>{{ $slot }}</div>
resources/views/components/ui/empty/content.blade.php Blade
<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