EmptyState
Placeholder for empty lists, coming-soon sections, or feature-flagged areas. Located at apps/web/components/ui/empty-state.tsx.
Usage
<EmptyState
icon={<IconFolder />}
message={<><strong>No files yet.</strong> Upload docs to give the AI context.</>}
action={
<Button variant="primary" size="sm" onClick={() => setShowUpload(true)}>
<IconPlus /> Upload file
</Button>
}
/>
Props
interface EmptyStateProps {
message: ReactNode; // required, shown centered
icon?: ReactNode; // optional, above message
action?: ReactNode; // optional row below message (buttons)
variant?: "dashed" | "solid";
size?: "sm" | "md" | "lg";
className?: string;
}
Variants
dashed (default)
Dashed border, softer background. For coming-soon, placeholder, intentionally-empty areas.
<EmptyState
icon={<IconFolder />}
message="No projects yet"
/>
solid
Solid background surface, stronger presence. For "load failed" or action-required empties.
<EmptyState
variant="solid"
icon={<IconAlert />}
message={<><strong>Failed to load.</strong> Try refreshing.</>}
action={<Button variant="primary" size="sm">Retry</Button>}
/>
Sizes
| size | Padding |
|---|---|
| sm | p-4 |
| md (default) | p-6 |
| lg | p-12 |
Use lg for full-page empties (no data at all). Use sm for inline empties inside a smaller panel.
Message shape
Message is ReactNode, not just string. Use inline formatting:
<EmptyState
message={
<>
<strong>No threads yet.</strong> Start a conversation using the composer below.
</>
}
/>
Bold the headline sentence, normal weight for the explanation.
Action
Single button OR a row of 2 buttons max. Don't stuff a lot of actions in an empty state — the empty state is about helping the user decide what to do next, not enumerate every possibility.
Don't
- Don't inline empty-state divs. Always use
<EmptyState>— it centralizes spacing, border, icon padding. - Don't use for error states that need action. For full error pages, use a dedicated error component.
- Don't pass a raw string with emoji. Use an
iconprop with an SVG for consistent rendering.