Design system

Empty state

Fills a library tab that has nothing to show, with one line on how things get there and the one action that starts it.

import { Empty } from "@/pages/library/LibraryPage";
import "@/pages/library/library.css";

Empty is the library's empty state: an icon tile for what belongs here, a title that says what is missing, one line on how it gets here, and one action. It fills the space left in its card and centres in it, so an empty tab reads as a finished screen rather than a broken one.

It is the product version of the empty states pattern. The pattern describes the rules; this is the component the Library page renders.

Nothing created yet

The first time someone opens a tab, name what is missing and offer the step that adds the first one.

import { Shapes } from "@phosphor-icons/react";

<Empty
  icon={Shapes}
  title="Your workspace has no components yet"
  text="Add components from the library, then publish the ones you want used."
  action={<Button variant="secondary" size="md" label="Browse the library" onClick={() => setScope("library")} />}
/>

Filters found nothing

When the list is not empty but the filters hide everything, say so and offer to clear them. Use the FunnelSimple icon so it cannot be mistaken for a first-use state.

<Empty
  icon={FunnelSimple}
  title="No components match these filters"
  text="Nothing has this status, folder and tag together."
  action={<Button variant="secondary" size="md" label="Clear filters" onClick={clearFilters} />}
/>

Without an icon

icon is optional. The editors leave it out when the thing someone opened is gone, because there is no section icon that fits.

<Empty
  title="This page no longer exists"
  text="It may have been removed, or the browser was reloaded."
  action={<Button variant="secondary" size="md" label="Back to the library" onClick={() => navigate("/library")} />}
/>

Without an action

action is optional too. Leave it out only when the next step is somewhere else on the screen, such as the chat beside the page builder's canvas.

<Empty
  title="Your page will appear here"
  text="It is built from the components you have published in the Library."
/>

Anatomy

PartClassStyle
Container.lib-emptyA centred column, at least 360px tall, 48px by 24px padding. It has flex: 1, so it fills the rest of a flex column.
Icon tile.lib-empty__icon48px square, 12px corners, --color-grey-50 fill, 1px --color-grey-100 border, 16px below. The icon is drawn at 22px in --color-grey-400.
Title.lib-empty__title16px, 24px line height, medium. It sets no colour of its own and takes the text colour around it.
Text.lib-empty__text14px, 20px line height, --color-grey-500 (#757A97), at most 380px wide, 4px below the title.
Action.lib-empty__action20px above. Whatever node you pass, normally one secondary Button at md.

Props

PropTypeDefaultNotes
iconPhosphor icon componentPass the component, not an element. Rendered at 22px. Leave it out for a state with no fitting icon.
titleReactNodeWhat is missing, in one line.
textReactNodeHow things get here, in one or two sentences.
actionReactNodeUsually one Button. Leave it out when there is nothing to do here.

There is no className or other prop. The component renders <span> elements for the title and text, not headings or paragraphs.

Accessibility

  • The title and text are plain <span> elements. A screen reader reads them in order, but the title is not a heading, so it does not appear in a heading list. If the empty state is the only content of a page, the page's own <h1> has to carry the context.
  • The icon is decorative and has no label, which is right: the title says the same thing.
  • The action is a real Button, so it gets keyboard focus and the product focus ring.

Do and don't

  • Do write the title as what is missing ("Your workspace has no components yet"), not as an instruction.
  • Do give one action, and make it the step that fills the space: "Browse the library", "Clear filters", "Ask in chat".
  • Do use FunnelSimple and "Clear filters" for a filtered list, so people know their data is still there.
  • Don't put two buttons in action. If there are two ways in, pick the one most people need and mention the other in text.
  • Don't use it inside a small panel or a menu. It is at least 360px tall and meant to fill a tab.

When to use it

Use Empty inside a Library or editor card that has nothing to list. For a new screen outside the library, follow the empty states pattern with the same parts. There is no empty state component in @/ui yet.

Where it is used

FileState
src/pages/library/LibraryPage.jsx, WorkspaceComponents"Your workspace has no components yet" and "No components match these filters"
src/pages/library/LibraryPage.jsx, Templates"Your workspace has no templates yet" and "No templates match these filters"
src/pages/library/LibraryPage.jsx, PagesTab"No landing pages yet", with "Ask in chat"
src/pages/library/LibraryPage.jsx, CreativesTab"No ad creatives yet", with "Make an ad creative"

The page builder, the creative editor and the component editor (PageBuilderPage.jsx, CreativeEditorPage.jsx, ComponentEditorPage.jsx) write the .lib-empty classes by hand for their "no longer exists" states, and put .lib-empty__title and .lib-empty__text inside .lib-browser__empty for "Your page will appear here" and "Your creative will appear here". None of them use Empty, so a change to the component will not reach them.

On this page