apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
EmptyState
Variant↳ other

Feedback

EmptyState

Standard layout for empty lists, no-results, first-run, error, success, and loading surfaces. Compound primitive — pass icon/illustration/title/description/actions as props for the 80% case or use the .Icon / .Illustration / .Title / .Description / .Actions subparts for full control. Loading variant auto-injects <Spinner />.

EmptyState

The canonical layout for empty lists, no-results, first-run, error, success, and loading surfaces. Drop it anywhere the data could be absent: data tables, search results, dashboards, file lists, inboxes.

EmptyState is two APIs in one: the prop-driven shortcut form for the 80% case, and a compound form (<EmptyState.Icon> / .Illustration / .Title / .Description / .Actions) for full control. When any compound child is detected, the prop-driven shortcuts are silently ignored — the two modes never compete.

Overview — icon, copy, and a clear next step

Loading preview…
Overview.tsx

Anatomy

tsx
┌─────────────────────────────────────────────────────┐
│                       ◯                             │   ← <EmptyState.Icon> / .Illustration
│                                                      │
│                 No users yet                         │   ← <EmptyState.Title> (<h3> by default)
│      Invite your team to collaborate.                │   ← <EmptyState.Description> (<p>)
│                                                      │
│        [Invite teammates]  [Learn more]              │   ← <EmptyState.Actions> (flex row)
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│                       ◯                             │   ← <EmptyState.Icon> / .Illustration
│                                                      │
│                 No users yet                         │   ← <EmptyState.Title> (<h3> by default)
│      Invite your team to collaborate.                │   ← <EmptyState.Description> (<p>)
│                                                      │
│        [Invite teammates]  [Learn more]              │   ← <EmptyState.Actions> (flex row)
└─────────────────────────────────────────────────────┘

Variants

VariantRoot roleIcon container tintAuto glyph
defaultregion (with aria-labelledby → Title)neutral subtle—
erroralertdanger-subtle bg + danger fg—
loadingstatus + aria-busy="true" + aria-live="polite"neutral subtle<Spinner />
successregionsuccess-subtle bg + success fg—

The loading variant auto-injects a <Spinner /> in the icon slot only when neither icon nor illustration is supplied (nor a compound <EmptyState.Icon> child). Pass an explicit icon to override.

Sizes

SizeIcon containerTitle textPadding (y / x)When
sm40 × 40 pxtext-base24 / 16 pxInline use, sidebar panels.
md56 × 56 pxtext-lg40 / 24 pxDefault. Page-level empty states.
lg80 × 80 pxtext-xl64 / 32 pxHero empty surfaces, full-page.

Action shortcuts

Both primaryAction and secondaryAction accept the same shape:

ts
type ActionShortcut = {
  label: ReactNode;
  onClick?: () => void;
  href?: string;          // when set, renders as <Button asChild><a href=…>
  target?: string;        // anchor target, only meaningful with href
  rel?: string;           // anchor rel,    only meaningful with href
  variant?: 'solid' | 'outline' | 'ghost';   // default: primary='solid', secondary='ghost'
  color?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral';
  // …all other <Button> props
};
type ActionShortcut = {
  label: ReactNode;
  onClick?: () => void;
  href?: string;          // when set, renders as <Button asChild><a href=…>
  target?: string;        // anchor target, only meaningful with href
  rel?: string;           // anchor rel,    only meaningful with href
  variant?: 'solid' | 'outline' | 'ghost';   // default: primary='solid', secondary='ghost'
  color?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral';
  // …all other <Button> props
};

When target="_blank" is set without an explicit rel, the component injects rel="noopener noreferrer" automatically.

Examples

Minimal — title + description only

Loading preview…
Basic.tsx

With an icon

Loading preview…
WithIcon.tsx

With a larger illustration

Loading preview…
WithIllustration.tsx

Adding a primary call-to-action

Loading preview…
WithPrimaryAction.tsx

Primary + secondary (the secondary is a link)

Loading preview…
WithBothActions.tsx

variant=error — role=alert, danger-tinted icon

Loading preview…
Error.tsx

variant=loading — auto-injects <Spinner />, role=status

Loading preview…
Loading.tsx

variant=success — celebratory tone

Loading preview…
Success.tsx

size=sm — for inline use

Loading preview…
Compact.tsx

bordered — fills a rect inside a DataGrid / FileUpload

Loading preview…
Bordered.tsx

Compound API for full content control

Loading preview…
Compound.tsx

align=start — left-aligned for sidebar panels

Loading preview…
AlignStart.tsx

Props

<EmptyState />

PropTypeDefaultNotes
variant'default' | 'error' | 'loading' | 'success''default'Drives ARIA role + icon tint + (loading) auto-Spinner.
size'sm' | 'md' | 'lg''md'Density token; cascades to all subparts via context.
align'center' | 'start''center'Layout alignment. start is for inline use.
borderedbooleanfalseWraps in a dashed rounded border — for DataGrid empty slots.
paddedbooleantrueAdds outer padding sized to size. Disable for parent-owned spacing.
as'section' | 'div' | 'aside' | 'article''section'Root element. <section> is a landmark.
iconReactNode—Small glyph rendered in the icon container.
illustrationReactNode—Larger graphic; wins over icon when both are set.
titleReactNode—Becomes the accessible label.
descriptionReactNode—Becomes the accessible description.
primaryActionActionShortcut (see above)—Solid Button by default.
secondaryActionActionShortcut—Ghost Button by default.
classNamestring—Merged via tailwind-merge.
sxSx—Theme-aware inline style.

Subcomponents

SubcomponentElementNotes
<EmptyState.Icon><div>Tinted circular container; aria-hidden="true".
<EmptyState.Illustration><div>Max-width sized per size; aria-hidden="true".
<EmptyState.Title><h3> (default; override via as)The accessible name of the region.
<EmptyState.Description><p>The accessible description.
<EmptyState.Actions><div>Flex row for <Button> children.

All five subparts pull size / variant / align from EmptyStateContext.

Accessibility

  • Variant decides the role. default / success → region. error → alert. loading → status + aria-busy="true" + aria-live="polite".
  • Title is the accessible name. When prop-driven, the root auto-wires aria-labelledby to the auto-rendered <h3>. Compound mode + custom Title with an explicit id will be picked up too if the consumer passes <EmptyState aria-labelledby={myId}>.
  • Description is the accessible description. Same aria-describedby pattern.
  • Icon / illustration are decorative. Always aria-hidden="true" so they don't duplicate the title in the AT tree.
  • Actions are real buttons. <Button> / <Button asChild><a/> — full keyboard support, focus ring, native semantics.
  • href actions are safe by default. target="_blank" without rel injects rel="noopener noreferrer".
  • axe-core. Zero violations across variant × size × align, including compound mode and the auto-Spinner loading variant.

Theming

ts
defineTheme({
  components: {
    EmptyState: {
      defaultProps: { size: 'lg', bordered: true },
      styleOverrides: {
        root: 'bg-bg-paper',
        icon: 'shadow-sm',
        title: 'tracking-tight',
        description: 'leading-loose',
        actions: 'gap-3',
      },
    },
  },
});
defineTheme({
  components: {
    EmptyState: {
      defaultProps: { size: 'lg', bordered: true },
      styleOverrides: {
        root: 'bg-bg-paper',
        icon: 'shadow-sm',
        title: 'tracking-tight',
        description: 'leading-loose',
        actions: 'gap-3',
      },
    },
  },
});

Each of the six slots (root / icon / illustration / title / description / actions) is themable independently. defaultProps applies under both the prop-driven and compound APIs.

Integration patterns

SurfaceRecommended composition
Data table<EmptyState size="sm" bordered icon={…} title="No rows" primaryAction={{ label: 'Add row', … }} />
Search page<EmptyState illustration={…} title="No results" primaryAction={{ label: 'Clear filters' }} />
Dashboard<EmptyState size="lg" icon={…} title="No data yet" primaryAction={{ label: 'Connect a source' }} />
Error boundary<EmptyState variant="error" title="Something went wrong" primaryAction={{ label: 'Retry' }} />
Suspense fallback<EmptyState variant="loading" title="Loading workspace" />
First-run<EmptyState size="lg" illustration={…} title="Welcome!" primaryAction={{ label: 'Get started' }} />

DataGrid, FileUpload, and Combobox can consume EmptyState as their default empty / no-results slot content.

Props

PropTypeDefaultDescription
alignResponsiveValue<EmptyStateAlign>'center'Layout alignment.
asenum—Element name for the root. Semantic landmark — `<section>` is the default so screen-reader users can navigate to / from the empty region.
borderedbooleanfalseWrap the root in a dashed border container. The right choice for DataGrid-style empty slots where the EmptyState needs to fill an existing rectangle.
descriptionReactNode—Description text or node. Becomes the accessible description (`aria-describedby`).
iconReactNode—Small leading glyph rendered inside the icon container. Mutually exclusive with `illustration`.
illustrationReactNode—Larger illustration (replaces the icon container entirely). Wins over `icon` if both set.
paddedbooleantruePad the root vertically per size token. Disable for inline use in a layout that already provides its own padding.
primaryActionEmptyStateActionShortcut—Primary CTA. `{ label, onClick? | href?, ...buttonProps }`.
secondaryActionEmptyStateActionShortcut—Secondary CTA. Same shape as `primaryAction`.
sizeResponsiveValue<EmptyStateSize>'md'Density token.
sxSx—Theme-aware inline style object (resolves palette / spacing / radius tokens to CSS vars).
titleReactNode—Title text or node. Becomes the accessible label / heading.
variantResponsiveValue<EmptyStateVariant>'default'Visual + semantic family.