Empty State
Admin placeholder compound for lists, tables, search results, and dashboard regions with no data. Icon, heading, description, primary and secondary action slots. Built with CSS Modules and container queries — copy it into your project and own it completely.
npx visor add empty-stateBasic
No profiles yet
With Description
No profiles yet
Create your first profile to get started.
With Action
No profiles yet
Create your first profile to get started.
With Primary and Secondary Actions
No profiles yet
Create your first profile to get started.
Tones
Default tone
Dashed border on a muted surface.
Subtle tone
Borderless, centered content only.
Sizes
Small
Compact vertical rhythm.
Medium
Default vertical rhythm.
Large
Spacious vertical rhythm.
Installation
npx visor add empty-stateThis copies two files into your project:
components/ui/empty-state/empty-state.tsx— the componentcomponents/ui/empty-state/empty-state.module.css— the styles
Usage
import { EmptyState } from '@/components/ui/empty-state/empty-state';
import { Button } from '@/components/ui/button/button';
export default function Example() {
return (
<EmptyState
heading="No profiles yet"
description="Create your first profile to get started."
action={<Button>New profile</Button>}
/>
);
}API Reference
EmptyStateProps
| Prop | Type | Default | Description |
|---|---|---|---|
heading* | React.ReactNode | — | Short direct statement of the empty condition. Rendered in the element given by headingAs. |
icon | React.ReactNode | — | Leading visual, typically a Phosphor icon. Marked aria-hidden since the heading text carries the meaning. |
description | React.ReactNode | — | 1-2 sentence explanation or guidance beneath the heading. |
action | React.ReactNode | — | Primary CTA slot. Typically a Button. |
secondaryAction | React.ReactNode | — | De-emphasized fallback action. Typically a Button. |
size | 'sm' | 'md' | 'lg' | 'md' | Controls padding, icon size, heading size, and overall vertical rhythm. |
tone | 'default' | 'subtle' | 'default' | Default uses a dashed border on a muted surface. Subtle is borderless. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Heading element used for the heading slot. |
className | string | — | Additional CSS class names to merge onto the root element. |
...props | React.HTMLAttributes<HTMLDivElement> | — | All standard HTML attributes are forwarded to the root div. |
The component also accepts all standard HTML attributes for the root <div>.
Source Files
After running npx visor add empty-state, you'll have:
empty-state.tsx
A forwardRef component using CVA for size and tone variants and CSS Modules for layout. The root is a <div> with role="status" so assistive tech announces the empty state as a state change. The heading element is controlled by headingAs, and the icon slot is marked aria-hidden since the heading text carries the meaning.
empty-state.module.css
All values use CSS custom properties from @loworbitstudio/visor-core, so the empty state automatically adapts to your active theme. The default tone uses border-style: dashed with --border-default. The component uses container-type: inline-size so the action cluster stacks vertically inside narrow containers without relying on viewport media queries.
Customization
After copying the component, you own it completely. Common customizations:
- Add an
illustrationslot for larger SVG artwork above the icon. - Swap the default
role="status"forrole="region"witharia-labelwhen the empty state is a primary landmark. - Extend tones to include a
destructivevariant for error states.
Data Table
A generic Tanstack-powered data table compound that composes Visor's table primitive. Handles sorting, pagination, row selection, global filter, sticky header, loading, and empty states. Built with CSS Modules — copy it into your project and own it completely.
Filter Bar
Admin filter bar compound — search input, filter controls slot, removable active-filter chips, results count, and a clear-all affordance above a data-table. Built with CSS Modules and container queries — copy it into your project and own it completely.