Empty State
Centered placeholder for a region with nothing in it, with room for the action that fills it. Upstream calls this component `empty`.
Added 14 October 2026
React
Codecomponents/react/empty-state-demo.tsx
import {
Button,
EmptyState,
EmptyStateContent,
EmptyStateDescription,
EmptyStateHeader,
EmptyStateMedia,
EmptyStateTitle,
} from '@stroeer-labs/design-library/react';
import {FolderPlusIcon, MegaphoneIcon, SearchXIcon} from 'lucide-react';
export function EmptyStateDemo() {
return (
<div className="space-y-6">
<div className="space-y-2">
<p className="text-xs font-medium text-muted-foreground">Nothing created yet — offer the first action</p>
<EmptyState className="border">
<EmptyStateHeader>
<EmptyStateMedia variant="icon">
<FolderPlusIcon />
</EmptyStateMedia>
<EmptyStateTitle>No campaigns yet</EmptyStateTitle>
<EmptyStateDescription>Create your first campaign to start booking inventory.</EmptyStateDescription>
</EmptyStateHeader>
<EmptyStateContent className="flex-row justify-center gap-2">
<Button>Create campaign</Button>
<Button variant="outline">Import</Button>
</EmptyStateContent>
</EmptyState>
</div>
<div className="space-y-2">
<p className="text-xs font-medium text-muted-foreground">Nothing matched — announced, because it replaced content</p>
{/* role="status" only when the empty state REPLACES content in place; an empty state that is
the initial page content must not be a live region. See docs/components/empty-state.md. */}
<EmptyState role="status" className="border">
<EmptyStateHeader>
<EmptyStateMedia variant="icon">
<SearchXIcon />
</EmptyStateMedia>
<EmptyStateTitle>No sites match these filters</EmptyStateTitle>
<EmptyStateDescription>Try widening the venue type or removing the date range.</EmptyStateDescription>
</EmptyStateHeader>
<EmptyStateContent>
<Button variant="outline">Clear all filters</Button>
</EmptyStateContent>
</EmptyState>
</div>
<div className="space-y-2">
<p className="text-xs font-medium text-muted-foreground">
Section-level, icon-lg media — borderless on purpose; the accent tint sits on the icon, never the well
</p>
<EmptyState>
<EmptyStateHeader>
<EmptyStateMedia variant="icon-lg" className="text-brand-interactive">
<MegaphoneIcon />
</EmptyStateMedia>
<EmptyStateTitle>No active campaigns</EmptyStateTitle>
<EmptyStateDescription>Book your first campaign and it will show up here.</EmptyStateDescription>
</EmptyStateHeader>
<EmptyStateContent>
<Button>Book a campaign</Button>
</EmptyStateContent>
</EmptyState>
</div>
<div className="space-y-2">
<p className="text-xs font-medium text-muted-foreground">Without media, no border — inline inside a panel</p>
<EmptyState>
<EmptyStateHeader>
<EmptyStateTitle>No line items</EmptyStateTitle>
<EmptyStateDescription>Add a line item to allocate budget.</EmptyStateDescription>
</EmptyStateHeader>
</EmptyState>
</div>
</div>
);
}Angular
Codecomponents/angular/empty-state-demo.ts
import {Component} from '@angular/core';
import {HlmButtonImports, HlmEmptyStateImports} from '@stroeer-labs/design-library/angular';
import {NgIcon, provideIcons} from '@ng-icons/core';
import {lucideFolderPlus, lucideMegaphone, lucideSearchX} from '@ng-icons/lucide';
@Component({
selector: 'dl-empty-state-demo',
standalone: true,
imports: [HlmEmptyStateImports, HlmButtonImports, NgIcon],
providers: [provideIcons({lucideFolderPlus, lucideMegaphone, lucideSearchX})],
template: `
<div class="space-y-6">
<div class="space-y-2">
<p class="text-xs font-medium text-muted-foreground">Nothing created yet — offer the first action</p>
<hlm-empty-state class="border">
<hlm-empty-state-header>
<hlm-empty-state-media variant="icon">
<ng-icon name="lucideFolderPlus" />
</hlm-empty-state-media>
<div hlmEmptyStateTitle>No campaigns yet</div>
<div hlmEmptyStateDescription>Create your first campaign to start booking inventory.</div>
</hlm-empty-state-header>
<hlm-empty-state-content class="flex-row justify-center gap-2">
<button hlmBtn>Create campaign</button>
<button hlmBtn variant="outline">Import</button>
</hlm-empty-state-content>
</hlm-empty-state>
</div>
<div class="space-y-2">
<p class="text-xs font-medium text-muted-foreground">Nothing matched — announced, because it replaced content</p>
<!-- role="status" only when the empty state REPLACES content in place; an empty state that is
the initial page content must not be a live region. See docs/components/empty-state.md. -->
<hlm-empty-state role="status" class="border">
<hlm-empty-state-header>
<hlm-empty-state-media variant="icon">
<ng-icon name="lucideSearchX" />
</hlm-empty-state-media>
<div hlmEmptyStateTitle>No sites match these filters</div>
<div hlmEmptyStateDescription>Try widening the venue type or removing the date range.</div>
</hlm-empty-state-header>
<hlm-empty-state-content>
<button hlmBtn variant="outline">Clear all filters</button>
</hlm-empty-state-content>
</hlm-empty-state>
</div>
<div class="space-y-2">
<p class="text-xs font-medium text-muted-foreground">
Section-level, icon-lg media — borderless on purpose; the accent tint sits on the icon, never the well
</p>
<hlm-empty-state>
<hlm-empty-state-header>
<hlm-empty-state-media variant="icon-lg" class="text-brand-interactive">
<ng-icon name="lucideMegaphone" />
</hlm-empty-state-media>
<h2 hlmEmptyStateTitle>No active campaigns</h2>
<div hlmEmptyStateDescription>Book your first campaign and it will show up here.</div>
</hlm-empty-state-header>
<hlm-empty-state-content>
<button hlmBtn>Book a campaign</button>
</hlm-empty-state-content>
</hlm-empty-state>
</div>
<div class="space-y-2">
<p class="text-xs font-medium text-muted-foreground">Without media, no border — inline inside a panel</p>
<hlm-empty-state>
<hlm-empty-state-header>
<div hlmEmptyStateTitle>No line items</div>
<div hlmEmptyStateDescription>Add a line item to allocate budget.</div>
</hlm-empty-state-header>
</hlm-empty-state>
</div>
</div>
`,
})
export class EmptyStateDemoComponent {}