Empty
What a region shows when it loaded and has nothing: why, and what to do next.
In Content first
See it live
npm run storybookWhen 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 firstWhat it is for
The picture shown as it is: an image or illustration.
In Content first
Attention belongs to the text: the invitation is in words, not a picture.
iconvariantUsed in Content firstWhat it is for
The picture as an icon in a small tile.
In Content first
A quiet outline icon in a small tile, above a warm invitation to save an article.
How Content first uses it
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.
human.q11The answer
Attention belongs to the text.
2So Empty
- –does not use default: Attention belongs to the text: the invitation is in words, not a picture.
Button: large Field: horizontal Item: muted3If it changed
and 8 answers that follow from it: Surface, text, action and status, Only what floats, One, and 5 more.
brand.q21The answer
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
and 1 answer that follow from it: Only when the meaning changes.
iconography.definition1The answer
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
No other answer follows from it.
voice.values1The answer
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.
Toast: neutral Toast: success Toast: danger3If it changed
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: Savecontent.action.cancel: Not nowcontent.action.destroy: Removecontent.action.retry: Try againcontent.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.
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.
| Part | Its token and the semantic it aliases | Value |
|---|---|---|
| Empty | ||
| Gap | component.empty.gapspace.gap.sectionvar(--empty-gap) | space.64:4rem |
| Corner | component.empty.radiusradius.containervar(--empty-radius) | radius.12:0.75rem |
| Padding | component.empty.paddingcomponent-spacing-map.x6var(--empty-padding) | space.36:2.25rem |
| Padding 2 | component.empty.padding-2component-spacing-map.x12var(--empty-padding-2) | space.64:4rem |
| Header | ||
| Gap | component.empty.header.gapspace.gap.defaultvar(--empty-header-gap) | space.16:1rem |
| Icon | ||
| Corner | component.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 size | component.empty.title.font-sizetype.heading.font-sizevar(--empty-title-font-size) | font.size.28:1.75rem |
| Weight | component.empty.title.font-weighttype.label.font-weightvar(--empty-title-font-weight) | font.weight.600:600 |
| Description | ||
| Text size | component.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 | ||
| Gap | component.empty.content.gapspace.gap.groupvar(--empty-content-gap) | space.32:2rem |
| Text size | component.empty.content.font-sizetype.body.font-sizevar(--empty-content-font-size) | font.size.18:1.125rem |