---
title: Empty
description: Present an empty or unavailable application state with optional
  media and actions.
package: moraine
version: 0.6.0
repository: https://github.com/subframe7536/moraine
---

# Empty

> Present an empty or unavailable application state with optional media and actions.

Empty is an application-level presentation primitive. It provides a consistent layout for an empty-state message, optional media, and actions. Your application decides when to show it; Empty does not inspect collections or search results.

## Basic usage

```tsx
import { Empty } from 'moraine'

export function Example() {
  return (
    <Empty>
      <Empty.Title>No projects yet</Empty.Title>
      <Empty.Description>Create a project to organize your work.</Empty.Description>
    </Empty>
  )
}
```

## Anatomy

```text
Empty [component; slot=root]
├── Empty.Media [part; slot=media]
├── Empty.Title [part; slot=title]
├── Empty.Description [part; slot=description]
└── Empty.Actions [part; slot=actions]
```

## Usage

Compose only the parts you need. All parts belong inside `Empty`. `Media` accepts arbitrary JSX, including Icon, Avatar, AvatarGroup, and images; it does not impose a fixed media size or background.

`Empty.Title` renders a `div` by default. Choose an explicit heading such as `as="h2"` when the surrounding page needs one. Empty adds no role or ARIA relationships automatically.

The `sm`, `md`, and `lg` sizes change padding, spacing, typography, and action gaps together. Use `sm` within a Card or table, `md` for ordinary content, and `lg` for a page-level empty state. Vertical padding is more generous than horizontal insets. Title and description stay grouped, with more space around media and actions. Size does not resize the content placed inside `Media`.

## Examples

### Basic empty state

```tsx
import { Empty, Icon } from 'moraine'

export function Basic() {
  return (
    <Empty>
      <Empty.Media>
        <Icon name="i-lucide-folder-open" class="text-muted-foreground size-8" />
      </Empty.Media>
      <Empty.Title>No projects yet</Empty.Title>
      <Empty.Description>Create a project to organize your work.</Empty.Description>
    </Empty>
  )
}
```

### Actions

Use actions to offer the next useful step. Wire them to your application's handlers.

```tsx
import { Button, Empty, Icon } from 'moraine'

export function Actions() {
  return (
    <Empty>
      <Empty.Media>
        <Icon name="i-lucide-inbox" class="text-muted-foreground size-8" />
      </Empty.Media>
      <Empty.Title>Your inbox is empty</Empty.Title>
      <Empty.Description>Start a conversation with your team.</Empty.Description>
      <Empty.Actions>
        <Button>New message</Button>
        <Button as="a" href="#basic-usage" variant="outline">
          Learn more
        </Button>
      </Empty.Actions>
    </Empty>
  )
}
```

### No search results

Your search logic decides when to render this message.

```tsx
import { Button, Empty, Icon, Input } from 'moraine'
import { createSignal } from 'solid-js'

export function NoResults() {
  const [search, setSearch] = createSignal('design systems')
  return (
    <div class="max-w-md w-full">
      <Input
        value={search()}
        onInput={(event) => setSearch(event.currentTarget.value)}
        aria-label="Search projects"
        placeholder="Search projects"
      />
      <Empty size="sm">
        <Empty.Media>
          <Icon name="i-lucide-search" class="text-muted-foreground size-6" />
        </Empty.Media>
        <Empty.Title>No matching projects</Empty.Title>
        <Empty.Description>Try a different keyword or clear your search.</Empty.Description>
        <Empty.Actions>
          <Button variant="outline" size="sm" disabled={!search()} onClick={() => setSearch('')}>
            Clear search
          </Button>
        </Empty.Actions>
      </Empty>
    </div>
  )
}
```

### Rich media

Compose avatars or your own illustration without changing Empty's layout API.

```tsx
import { AvatarGroup, Button, Empty } from 'moraine'

export function RichMedia() {
  return (
    <Empty>
      <Empty.Media>
        <AvatarGroup
          items={[
            { text: 'Alex', alt: 'Alex' },
            { text: 'Sam', alt: 'Sam' },
            { text: 'Jo', alt: 'Jo' },
          ]}
          size="lg"
        />
      </Empty.Media>
      <Empty.Title>No team members yet</Empty.Title>
      <Empty.Description>Invite your collaborators to work together.</Empty.Description>
      <Empty.Actions>
        <Button>Invite members</Button>
      </Empty.Actions>
    </Empty>
  )
}
```

### Sizes

```tsx
import { Empty, Icon } from 'moraine'
import { For } from 'solid-js'

export function Sizes() {
  return (
    <div class="w-full divide-border divide-y">
      <For each={['sm', 'md', 'lg'] as const}>
        {(size) => (
          <Empty size={size}>
            <Empty.Media>
              <Icon name="i-lucide-files" class="text-muted-foreground size-6" />
            </Empty.Media>
            <Empty.Title>No files ({size})</Empty.Title>
            <Empty.Description>Uploaded files will appear here.</Empty.Description>
          </Empty>
        )}
      </For>
    </div>
  )
}
```

### Custom container styling

Empty has no default border, background, shadow, or fixed height. Apply container styling through `class` and `style`; use root `classes` and `styles` to customize individual parts throughout the family. A part's own `class` and `style` take precedence over those family overrides.

```tsx
import { Button, Empty, Icon } from 'moraine'

export function CustomContainer() {
  return (
    <Empty
      as="section"
      aria-label="Saved reports"
      class="border border-border rounded-xl border-dashed bg-muted/30"
      classes={{ description: 'max-w-xs' }}
    >
      <Empty.Media>
        <Icon name="i-lucide-chart-no-axes-combined" class="text-muted-foreground size-8" />
      </Empty.Media>
      <Empty.Title as="h2">No saved reports</Empty.Title>
      <Empty.Description>
        Save a report to revisit the metrics that matter to you.
      </Empty.Description>
      <Empty.Actions>
        <Button variant="outline">Create report</Button>
      </Empty.Actions>
    </Empty>
  )
}
```

## Props

### Empty

Props for Empty.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| size | 'sm' \| 'md' \| 'lg' \| undefined | 'md' | Density and typography of all Empty parts. |
| as | T \| undefined | 'div' | Element or component to render as. |
| children | JSX.Element \| undefined | — | Empty-state parts and direct content. |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| classes | Classes \| undefined | — | Family slot class defaults for this instance. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |
| styles | Styles \| undefined | — | Family slot style defaults for this instance. |

### Empty.Media

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| as | T \| undefined | 'div' | Element or component to render as. |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### Empty.Title

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| as | T \| undefined | 'div' | Element or component to render as. |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### Empty.Description

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| as | T \| undefined | 'p' | Element or component to render as. |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |

### Empty.Actions

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| as | T \| undefined | 'div' | Element or component to render as. |
| children | JSX.Element \| undefined | — | — |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |
