Skip to main content

Empty

What a region shows when it loaded and has nothing: why, and what to do next.

In Content first

See it live​

Start Storybook to see it live: npm run storybook

When to use it​

In Content first: An empty reading list, as an invitation to save an article. Not while article text is loading.

  • Use it when: An empty list, a search with no results, or a first visit with no items yet.
  • Not when: Still loading (skeleton or spinner), or a load that failed (alert).

Instead:

Variants​

What each variant is for in any design system, then what Content first decided and why. Each sample is the live story with that variant set.

defaultvariantNot used in Content first
Start Storybook to see it live

What it is for

The picture shown as it is: an image or illustration.

✓A larger empty area where a picture helps.
✕A small region where the picture would push the words away.

In Content first

Attention belongs to the text: the invitation is in words, not a picture.

iconvariantUsed in Content first
Start Storybook to see it live

What it is for

The picture as an icon in a small tile.

✓A compact empty state next to data.
✕A first visit that needs more warmth than an icon.

In Content first

A quiet outline icon in a small tile, above a warm invitation to save an article.

How Content first uses it​

An empty state
Use An invitation, not a dead end
Not "No items"
An empty list is a chance to show what the product offers. Example: “Nothing saved yet. Tap Save on any article to read it later.”

Why it works this way​

Empty looks the way it does because of 4 answers about this product. Each card is one answer, in three steps: the answer, what it decided for Empty, and what would move if it changed.

What must the interface protect?human.q1
1The answer
Attention on the content

Attention belongs to the text.

2So Empty
  • –does not use default: Attention belongs to the text: the invitation is in words, not a picture.
It also shapes Button: large Field: horizontal Item: muted
3If it changed
2
Empty decisions would reopen

and 8 answers that follow from it: Surface, text, action and status, Only what floats, One, and 5 more.

How visible is the brand in the interface?brand.q2
1The answer
Present

The brand shows in type, not in color.

2So Empty
  • –does not use default: Attention belongs to the text: the invitation is in words, not a picture.
3If it changed
1
Empty decision would reopen

and 1 answer that follow from it: Only when the meaning changes.

What icon style?iconography.definition
1The answer
Outline

Outline icons stay quiet beside text.

2So Empty
  • ✓uses icon: A quiet outline icon in a small tile, above a warm invitation to save an article.
3If it changed
1
Empty decision would reopen

No other answer follows from it.

How does it sound?voice.values
1The answer
Conversational

Talk to one reader, like a good editor.

2So Empty
  • ✓uses icon: A quiet outline icon in a small tile, above a warm invitation to save an article.
It also shapes Toast: neutral Toast: success Toast: danger
3If it changed
1
Empty decision would reopen

No other answer follows from it.

Words​

  • Write to one person: "you", not "users".
  • Short sentences with room to breathe.
  • Explain why before asking for anything.
  • Use: Read, Save for later, You, Next issue. Avoid: Click here, Submit, Users, Content.

The labels components use, from the voice tokens:

  • content.action.commit: Save
  • content.action.cancel: Not now
  • content.action.destroy: Remove
  • content.action.retry: Try again
  • content.action.empty-action: Browse guides

See Voice and tone for the whole voice.

Use it in code​

npx open-ds add empty

The component reads only component keys and semantic tokens, so it follows the brand and mode on <html>.

Tokens it reads​

Empty reads 15 tokens of its own, each an alias of a semantic token. Each row is one part: its own token, the semantic it aliases, and the value it ends at. Change a semantic and every component that reads it follows; override one of Empty's tokens to change Empty alone.

Changes with theme

Content first decides what changes when you switch theme. Here a switch changes colors only, so the marked rows differ between light and dark; everything else holds steady.

PartIts token and the semantic it aliasesValue
Empty
Gapcomponent.empty.gapspace.gap.sectionvar(--empty-gap)space.64:4rem
Cornercomponent.empty.radiusradius.containervar(--empty-radius)radius.12:0.75rem
Paddingcomponent.empty.paddingcomponent-spacing-map.x6var(--empty-padding)space.36:2.25rem
Padding 2component.empty.padding-2component-spacing-map.x12var(--empty-padding-2)space.64:4rem
Header
Gapcomponent.empty.header.gapspace.gap.defaultvar(--empty-header-gap)space.16:1rem
Icon
Cornercomponent.empty.icon.radiusradius.containervar(--empty-icon-radius)radius.12:0.75rem
Fill
Changes with theme
component.empty.icon.backgroundcolor.surface.sunkenvar(--empty-icon-background)Lightstone.light.100:#F6F2EADarkstone.dark.100:#2E2A24
Text
Changes with theme
component.empty.icon.labelcolor.text.primaryvar(--empty-icon-label)Lightstone.light.900:#1F1B16Darkstone.dark.900:#FFFDF8
Title
Text sizecomponent.empty.title.font-sizetype.heading.font-sizevar(--empty-title-font-size)font.size.28:1.75rem
Weightcomponent.empty.title.font-weighttype.label.font-weightvar(--empty-title-font-weight)font.weight.600:600
Description
Text sizecomponent.empty.description.font-sizetype.body.font-sizevar(--empty-description-font-size)font.size.18:1.125rem
Text
Changes with theme
component.empty.description.labelcolor.text.mutedvar(--empty-description-label)Lightstone.light.600:#61594DDarkstone.dark.400:#A39A8B
Label 2
Changes with theme
component.empty.description.label-2color.action.primaryvar(--empty-description-label-2)Lightnavy.light.600:#1E3A5FDarknavy.dark.600:#8FA6C4
Content
Gapcomponent.empty.content.gapspace.gap.groupvar(--empty-content-gap)space.32:2rem
Text sizecomponent.empty.content.font-sizetype.body.font-sizevar(--empty-content-font-size)font.size.18:1.125rem