VisorVisor
ComponentsAdmin

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-state

Basic

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-state

This copies two files into your project:

  • components/ui/empty-state/empty-state.tsx — the component
  • components/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

PropTypeDefaultDescription
heading*React.ReactNodeShort direct statement of the empty condition. Rendered in the element given by headingAs.
iconReact.ReactNodeLeading visual, typically a Phosphor icon. Marked aria-hidden since the heading text carries the meaning.
descriptionReact.ReactNode1-2 sentence explanation or guidance beneath the heading.
actionReact.ReactNodePrimary CTA slot. Typically a Button.
secondaryActionReact.ReactNodeDe-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.
classNamestringAdditional CSS class names to merge onto the root element.
...propsReact.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 illustration slot for larger SVG artwork above the icon.
  • Swap the default role="status" for role="region" with aria-label when the empty state is a primary landmark.
  • Extend tones to include a destructive variant for error states.