# Empty states

What to show when there is nothing to show, and how to write it so the user knows what to do next.

An empty state appears whenever a list, table, chart or panel has no content. It is a moment to
explain, not a dead end: the user should always learn why the area is empty and what they can
do about it.

## Kinds of empty state

These look alike and are not. Each needs its own message, and often its own component.

| Kind           | When                                           | Tell the user                                         | Component                                                                            |
| -------------- | ---------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Nothing yet    | First use: no data has arrived or been created | What will appear here, and how to get it there        | [List Empty State](/patterns/list-empty-state) with an action                        |
| No results     | A search or filter matched nothing             | That the filters caused it, and how to widen them     | [List Empty State](/patterns/list-empty-state) with Clear filters                    |
| All done       | The user cleared a queue                       | That the work is finished                             | [List Empty State](/patterns/list-empty-state), no action                            |
| Failed to load | The request failed                             | That something went wrong and that they can try again | [List Error State](/patterns/list-error-state)                                       |
| No permission  | The user lacks the permission for this area    | That access is restricted and who can grant it        | The app's access-denied state on [List Empty State](/patterns/list-empty-state)      |
| Not contracted | The tenant does not have the module            | That the module is not part of their plan             | The app's module-unavailable state on [List Empty State](/patterns/list-empty-state) |

The last three are not "no data": showing "No alerts yet" for a failed request or a missing
permission tells the user something false.

```tsx
import { ListEmptyState } from "@zenoxai/ui/patterns/list-empty-state"

function ListEmptyStateDemo() {
  return (
    <ListEmptyState
      title="No alerts yet"
      description="Alerts appear here as soon as a connector reports a detection."
    />
  )
}
```

## Writing

- **Title:** why the area is empty, in one short line, without a period. "No alerts yet",
  "No assets match these filters".
- **Description:** what happens next or what to do, in one to three full sentences, with
  periods. "New alerts appear here as soon as a detection rule matches."
- Name the object ("alerts", "assets"), not "items", "data" or "results" when you can.
- Keep it neutral. An empty state is not an error and not a celebration.

## Actions

- Offer an action when there is one clear next step: create the first rule, clear the filters,
  try again.
- Use an `outline` button or a link, never the `default` (primary) variant: the empty state is not
  the main task of the page.
- Show the action only if the user has the permission for it. Otherwise explain who can.
- One action. Two at most when they are true alternatives (Create rule, Import rules).

```tsx
import { FilterXIcon, SearchXIcon } from "lucide-react"
import { Button } from "@zenoxai/ui/components/button"
import { ListEmptyState } from "@zenoxai/ui/patterns/list-empty-state"

function ListEmptyStateFiltered() {
  return (
    <ListEmptyState
      icon={SearchXIcon}
      title="No alerts match these filters"
      description="Try a wider time range or remove a filter."
      action={
        <Button variant="outline" size="sm">
          <FilterXIcon data-icon="inline-start" />
          Clear filters
        </Button>
      }
    />
  )
}
```

## Errors

A failed request uses [List Error State](/patterns/list-error-state): it says the data could not
be loaded and offers Try again. Never put the raw error message in it; log it instead.

```tsx
import { ListErrorState } from "@zenoxai/ui/patterns/list-error-state"

function ListErrorStateDemo() {
  return <ListErrorState onRetry={() => {}} />
}
```

## Size and repetition

- In small containers (a card, a sidebar panel), drop the icon and keep only the text.
- When several areas of one screen are empty for the same reason (a new tenant, a filter that
  matches nothing), show one message for the screen instead of one per card.
- Keep the table header visible above an empty table, so the user still sees what the table
  would contain.
