# EmptyState

> Centered placeholder for empty lists, empty search results, and zero-state dashboards. Optional icon, title, description, and action slots.

- Category: data-display
- Status: stable (since 1.0.0)
- A11y pattern: https://www.w3.org/WAI/ARIA/apg/patterns/
- Tokens: --foreground-primary, --foreground-secondary
- Playground: https://design.freecodecamp.org/playground#empty-state
- npm dependencies: `react@>=18 <20`
- Registry dependencies: [theme](https://design.freecodecamp.org/registry/theme.md)
- Files:
  - `EmptyState.tsx` → `src/ui/empty-state/EmptyState.tsx` (raw: https://design.freecodecamp.org/registry/empty-state/EmptyState.tsx)
  - `empty-state.css` → `src/ui/empty-state/empty-state.css` (raw: https://design.freecodecamp.org/registry/empty-state/empty-state.css)

## Install (copy source)

1. Ensure the theme is installed once per project - tokens.css + base.css imported globally, fonts available. See https://design.freecodecamp.org/registry/theme.md and https://design.freecodecamp.org/registry/starter.md.
2. Copy the files below into `src/ui/empty-state/` (adjust to your project layout) and import the CSS once from your global stylesheet, e.g. `@import './ui/empty-state/empty-state.css';`.
3. Colors, spacing and type come from tokens - tailor the component by editing the copied source; recolour by editing tokens.css, not the component CSS.

## Usage

Use EmptyState when a list, table, or result set has nothing to show.
Pair it with a single concrete action - the thing the user can do to
resolve the emptiness (file a task, import a CSV, connect an account).
Leave the action out for states that are informational only.

## Accessibility

The root is a plain `<div>` - pass `role="status"` if the empty state
replaces a freshly-loaded async region so assistive tech re-announces
the state change. The icon wrapper carries `aria-hidden="true"` so its
contents do not duplicate the title text.

## Example

```tsx
import { EmptyState } from './ui/empty-state/EmptyState';
import { Button } from './ui/button/Button';

<EmptyState
  title="No certifications yet"
  description="Pick a track and complete five projects."
  action={<Button variant="cta">Browse curriculum →</Button>}
/>
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `icon` | `ReactNode` | no | - |  |
| `title` | `ReactNode` | no | - |  |
| `description` | `ReactNode` | no | - |  |
| `action` | `ReactNode` | no | - |  |

## Source: EmptyState.tsx

```tsx
import React, { forwardRef } from 'react';

export interface EmptyStateProps extends Omit<
  React.HTMLAttributes<HTMLDivElement>,
  'title'
> {
  icon?: React.ReactNode;
  title?: React.ReactNode;
  description?: React.ReactNode;
  action?: React.ReactNode;
}

export const EmptyState = forwardRef<HTMLDivElement, EmptyStateProps>(
  (
    { icon, title, description, action, className = '', children, ...rest },
    ref
  ) => {
    const classes = ['empty-state', className].filter(Boolean).join(' ');
    return (
      <div ref={ref} className={classes} {...rest}>
        {icon !== undefined && (
          <div className='empty-state__icon' aria-hidden='true'>
            {icon}
          </div>
        )}
        {title !== undefined && <h3 className='empty-state__title'>{title}</h3>}
        {description !== undefined && (
          <p className='empty-state__description'>{description}</p>
        )}
        {children}
        {action !== undefined && (
          <div className='empty-state__action'>{action}</div>
        )}
      </div>
    );
  }
);
EmptyState.displayName = 'EmptyState';
```

## Source: empty-state.css

```css
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 48px 24px;
  text-align: center;
  color: var(--foreground-secondary);
}
.empty-state__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  color: var(--foreground-secondary);
}
.empty-state__icon > svg {
  width: 100%;
  height: 100%;
}
.empty-state__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--foreground-primary);
}
.empty-state__description {
  margin: 0;
  max-width: 42ch;
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.empty-state__action {
  margin-top: 8px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
```

## HTML / vanilla variant

```html
<div class="empty-state">
  <p class="empty-state__title">No certifications yet</p>
  <p class="empty-state__description">Pick a track…</p>
  <div class="empty-state__action"><a class="btn btn--cta">Browse →</a></div>
</div>
```

Interactive behaviours for plain HTML come from the vanilla runtime (data-uikit-* attributes): https://design.freecodecamp.org/registry/vanilla.md - or download https://design.freecodecamp.org/cdn/uikit.global.js once and self-host it (do not hotlink).

## For coding agents

This library is distributed as copyable source, not an npm package. Start at https://design.freecodecamp.org/registry/starter.md, discover components via https://design.freecodecamp.org/llms.txt, and copy files into the consuming project. Keep token names intact; recolour by editing the copied tokens.css.
