Skip to content
txbrendan/ui

Empty State

beta

What a list, table, or page shows when it has nothing in it yet: an icon, a title, a short description, and one or two actions.

Built from Button.

Preview

No projects yet

Projects keep your tasks, files, and people in one place. Create one to get started, or bring yours over from another app.

no results, size sm, inside a card

Invoices

No invoices match

Nothing was sent to this customer in March.

error, tone danger

Could not load invoices

Check your connection, then try again. Your invoices are safe.

no icon, no actions

Nothing to review

When someone asks for your review, it shows up here.

size lg, for a whole page

Your inbox is empty

Messages from your team and your customers land here.

narrow container

No projects yet

Create one to get started.

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the block. It also adds every component it uses. Its own files go in your components folder, and they are yours to edit.

    npx shadcn@latest add @txbrendan/empty-state

Use it for

  • A list, table, or page that has nothing in it yet. Say what will show up here and how to add the first one.
  • A search or filter that found nothing. Say so, and offer a way to clear the filter.
  • A section that failed to load. Set tone="danger" and offer a Retry button.
  • Pass one or two Buttons in actions, the main one first. With size="sm", pass size="sm" Buttons too.
  • Make every action an emphasis="ghost" Button, so the next step stays quiet. With two actions, give the main one tone="brand" so it stands apart.
  • Write the title so it makes sense without the icon, since screen readers skip the icon.

Avoid it for

  • Content that is still loading. Show a skeleton, then the empty state once you know there is nothing.
  • A setup checklist for someone's first visit. Use First Use instead.
  • A message about the whole app, like an outage or a billing problem. Use Alert.
  • More than two actions. Pick the one that matters, and link to the rest from the description.

Props

  • title

    What is missing, in a few words, like "No projects yet". Required.

    ReactNode

  • description

    One or two short sentences: what will show up here, and how to start.

    ReactNode

  • icon

    An icon element, like <FolderIcon />. It sits in a round tile above the title.

    ReactNode

  • actions

    One or two Buttons or links. Put the main one first. Below 18rem wide they stack.

    ReactNode

  • tone

    Color of the icon tile. Use "danger" when something failed to load.

    "neutral" | "brand" | "danger" | "success" | "warning" · default "neutral"

  • size

    sm inside a list or card, md for a section, lg for a whole page. Scales the space, icon, and text.

    "sm" | "md" | "lg" · default "md"

  • headingLevel

    The title's heading level, so it fits under the heading above it.

    1 | 2 | 3 | 4 | 5 | 6 · default 2

  • className

    Extra classes on the outer div, which holds the container query. Add a border here to frame it.

    string

Accessibility

  • The title is a real heading, an h2 by default. Set headingLevel to one below the heading the empty state sits under.
  • The icon tile is hidden from screen readers, so the title has to say what is missing on its own.
  • The block adds nothing that takes focus. Only the actions you pass are in the tab order, in the order you pass them.
  • Other props, like role or aria-label, go on the outer div.

Examples

Nothing here yet

<EmptyState
  icon={<FolderPlus />}
  title="No projects yet"
  description="Projects keep your tasks, files, and people in one place."
  actions={
    <>
      <Button tone="brand" emphasis="ghost">New project</Button>
      <Button emphasis="ghost">Import</Button>
    </>
  }
/>

A search with no results

<EmptyState
  size="sm"
  headingLevel={3}
  icon={<SearchX />}
  title="No matches"
  description="Nothing matches those filters."
  actions={<Button size="sm" emphasis="ghost">Clear filters</Button>}
/>

Failed to load

<EmptyState
  tone="danger"
  icon={<CircleAlert />}
  title="Could not load invoices"
  description="Check your connection, then try again."
  actions={<Button emphasis="ghost">Try again</Button>}
/>