---
title: Empty State
description: Placeholder for empty content areas
visibility: guest
draft: false
---

# Empty State

A centered placeholder for pages or panels with no content yet. Combines an icon, title, description, and optional call-to-action into a single cohesive block.

## Basic Usage

:::preview{title="Empty state with CTA"}
<x-wirekit::empty-state icon="info" title="No messages yet" description="Start a conversation to see messages here.">
  <x-wirekit::button>New Message</x-wirekit::button>
</x-wirekit::empty-state>
:::

## Variations

### Icon + Title Only

:::preview{title="Search returned nothing"}
<x-wirekit::empty-state icon="search" title="No results" description="Try a different search term." />
:::

### Custom Icon / Illustration

Use the `iconSlot` named slot for custom SVGs or images:

:::preview{title="Custom SVG via iconSlot"}
<x-wirekit::empty-state title="Nothing here" description="Add your first project to get started.">
    <x-slot:iconSlot>
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="height: 3rem; width: 3rem;" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /></svg>
    </x-slot:iconSlot>
    <x-wirekit::button intent="neutral" surface="outline">Create Project</x-wirekit::button>
</x-wirekit::empty-state>
:::

### Minimal (title only)

:::preview{title="Minimal — title only"}
<x-wirekit::empty-state title="Inbox empty" />
:::

## Surface Variants

By default the empty state has no container chrome — it sits directly in whatever surrounds it. Use `variant="outline"` for a dashed-bordered placeholder (reads as a drop zone) or `variant="muted"` for a filled muted card.

:::preview{title="Default, outline, and muted variants"}
<x-wirekit::stack gap="md">
    <x-wirekit::empty-state variant="outline" icon="inbox" title="No notifications" description="You're all caught up." />
    <x-wirekit::empty-state variant="muted" icon="search" title="No results" description="Try a different search term." />
</x-wirekit::stack>
:::

## When to Use

- **Empty search results** — tell users their query returned nothing
- **Empty lists / tables** — first-run experience before any data exists
- **Empty folders or collections** — invite the user to add content
- **Filtered-to-zero state** — combine with a "Clear filters" action

## Width & Layout

The empty state fills its parent width and centers its content. Constrain the width on the parent or directly:

```blade
<x-wirekit::empty-state class="max-w-md mx-auto" title="No results" description="Try a different search." />
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `icon` | string\|null | `null` | Semantic icon alias (e.g. `'info'`, `'search'`) |
| `title` | string\|null | `null` | Primary heading text |
| `description` | string\|null | `null` | Secondary descriptive text |
| `level` | int | `3` | Heading level (1–6) for the title. Match it to the surrounding document outline so screen-reader heading navigation stays correct |
| `variant` | string | `'default'` | Container chrome — `default` (none), `outline` (dashed border), `muted` (filled surface) |
| `animateIn` | string\|null | `null` | Entrance-reveal preset (e.g. `'fade'`, `'slide-up'`, `'scale-in'`) — inline alternative to wrapping in `<x-wirekit::reveal>`. See [docs/animations.md](../animations.md) for the full preset list. Respects `prefers-reduced-motion: reduce`. |
| `scope` | string\|null | `null` | Scoped personalization name |

### Slots

| Slot | Purpose |
| --- | --- |
| default | Call-to-action buttons or links |
| `iconSlot` | Custom icon/illustration (overrides `icon` prop) |

## Accessibility

- The title renders as a heading — `<h3>` by default. Set the `level` prop (1–6) to match the surrounding document outline so the heading hierarchy stays correct: `level="2"` directly under a page `<h1>`, or `level="1"` when the empty-state represents the whole page's main content
- The icon is purely decorative; its meaning is carried by the title + description. No `aria-label` is set on the icon wrapper
- Call-to-action buttons inside the default slot keep their own focus styles

## Keyboard Interaction

This component is purely presentational and does not respond to keyboard input.

## Design Tokens

| Element | Token |
| --- | --- |
| Container padding | `--padding-wk-x-xl` / `--padding-wk-y-xl` |
| Icon wrapper bg | `--color-wk-bg-muted` |
| Icon color | `--color-wk-text-muted` |
| Title color | `--color-wk-text` |
| Title size | `--text-wk-lg` |
| Description color | `--color-wk-text-muted` |
| Description size | `--text-wk-sm` |

## Customization

Empty State has no prop defaults (all props are contextual — you always pass `title` / `description` / `icon` inline). Visual customization happens on three levels:

1. **Inline props** — override per-usage (`icon`, `title`, `description`, `scope`)
2. **Design tokens** — change colors/spacing/typography globally by overriding `--color-wk-*`, `--padding-wk-*`, `--text-wk-*` in your `app.css`
3. **Scoped personalization** — target a specific usage with the `scope` prop and register class overrides via `WireKit::personalize()`

```php
// config/wirekit.php — personalize the empty-state component
'components' => [
    'empty-state' => [
        // no prop defaults — customize via tokens or scopes
    ],
],
```

To replace the entire component markup (advanced), publish the view:

```bash
php artisan vendor:publish --tag=wirekit-views
# then edit resources/views/vendor/wirekit/components/empty-state.blade.php
```

## Further Reading

- [Material Design — Empty States](https://m2.material.io/design/communication/empty-states.html)
- [WAI-ARIA: `aria-labelledby`](https://www.w3.org/TR/wai-aria-1.2/#aria-labelledby)
- [MDN: Heading elements (`<h1>`–`<h6>`)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements)
