Empty
A placeholder for empty states — no data, no search results, a blank list, or a first-run screen. It centers a piece of media (usually an icon), a title, a description, and optionally an action, inside a dashed container.
Empty is a compound component: compose the parts you need. Only the root is required; everything inside is optional.
Anatomy
Empty ← the dashed container; centers its content
├── EmptyHeader ← groups the media, title, and description
│ ├── EmptyMedia ← the icon or illustration (variant: icon | default)
│ ├── EmptyTitle ← the headline
│ └── EmptyDescription ← supporting text
└── EmptyContent ← optional area for actions (buttons, links)Usage
<script setup lang="ts">
import {
Empty,
EmptyHeader,
EmptyMedia,
EmptyTitle,
EmptyDescription,
} from "@dlbcodes/ui";
import { PhFolderOpen } from "@phosphor-icons/vue";
</script>
<template>
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<PhFolderOpen class="size-5" />
</EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>
You haven't created any projects. Get started by creating your
first one.
</EmptyDescription>
</EmptyHeader>
</Empty>
</template>
With an action
Use EmptyContent to add a call to action below the header.
<script setup lang="ts">
import {
Empty,
EmptyHeader,
EmptyMedia,
EmptyTitle,
EmptyDescription,
EmptyContent,
Button,
} from "@dlbcodes/ui";
import { PhPlus, PhRocket } from "@phosphor-icons/vue";
</script>
<template>
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<PhRocket class="size-5" />
</EmptyMedia>
<EmptyTitle>Launch your first campaign</EmptyTitle>
<EmptyDescription>
Campaigns help you reach your audience. Create one to begin.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="primary">
<PhPlus class="size-4" />
New campaign
</Button>
</EmptyContent>
</Empty>
</template>
No results
A common use is the "no search results" state.
<script setup lang="ts">
import {
Empty,
EmptyHeader,
EmptyMedia,
EmptyTitle,
EmptyDescription,
} from "@dlbcodes/ui";
import { PhMagnifyingGlass } from "@phosphor-icons/vue";
</script>
<template>
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<PhMagnifyingGlass class="size-5" />
</EmptyMedia>
<EmptyTitle>No results found</EmptyTitle>
<EmptyDescription>
We couldn't find anything matching your search. Try a different
term.
</EmptyDescription>
</EmptyHeader>
</Empty>
</template>
Media variants
EmptyMedia has two variants. icon wraps the media in a subtle rounded background; default (the default) renders the media as-is — useful for a larger icon or a custom illustration.
<script setup lang="ts">
import {
Empty,
EmptyHeader,
EmptyMedia,
EmptyTitle,
EmptyDescription,
} from "@dlbcodes/ui";
import { PhTray } from "@phosphor-icons/vue";
</script>
<template>
<Empty>
<EmptyHeader>
<EmptyMedia>
<PhTray class="size-10 text-text-tertiary" />
</EmptyMedia>
<EmptyTitle>Your inbox is empty</EmptyTitle>
<EmptyDescription>
New messages will appear here.
</EmptyDescription>
</EmptyHeader>
</Empty>
</template>
Appearance
The container's border uses the theme's --border, the icon background uses --muted, and the text uses --foreground and --muted-foreground. Corners follow --radius. So an empty state follows your theme, including dark mode. See Theming.
Props
EmptyMedia
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "icon" | "default" | icon adds a rounded subtle background; default is bare. |
class | string | — | Classes merged onto the media wrapper. |
Empty / EmptyHeader / EmptyTitle / EmptyDescription / EmptyContent
Each takes only class. Their slot content is what's rendered:
Empty— the dashed container; centers everything.EmptyHeader— groups media, title, and description.EmptyTitle— the headline text.EmptyDescription— supporting text. Links inside it are underlined and take the brand color on hover.EmptyContent— an area for actions, below the header.
Composition
Only Empty is required. Use as many or as few of the parts as you need — a title alone, or the full media + title + description + action stack.