# Empty State
Let users know when there is nothing to show.
Section: components
Docs: https://nebula.kaluza.com/components/empty-state
---
## When to use
- When users have navigated to the right place but there is nothing to display, for example a list with no items or a table with no results
- When a search or filter returns no matches
- In cards, table containers, or page sections that would otherwise be blank
## When not to use
- **For a null value within an otherwise populated list or table** - use a short negative phrase such as "None" inline in the cell instead.
- **While data is still loading** - use [Skeleton Loading](/components/skeleton-loading) instead. Showing an empty state before data has arrived misleads the user into thinking the data does not exist.
- **For error states** - Empty State must not be used for error states. Use [Notification](/components/notification) to communicate errors instead.
- **For 404 Page Not Found** - handle this at the application level, not with this component.
- **In very small or constrained spaces** - the component requires enough padding to breathe. If the available space cannot accommodate it, use plain text instead.
## Anatomy
1. **Icon**
2. **Title**
3. **Description**
4. **Action**
## Basic usage
```tsx
```
## Variants and features
### With action
An action gives users a direct path to resolve the empty state, for example adding the first item to an empty list or clearing a filter that returned no results.
Only include an action when it directly addresses the reason for the empty state. If completing the action would not populate the space, do not include it.
```tsx
alert('Submit meter read clicked'),
}}
/>
```
## Content
### Title
Every empty state must have a title. Use one of these two patterns:
- **For empty datasets:** "No [objects] on this [entity]", for example "No meter reads on this meter" or "No transactions on this account"
- **For search or filter results:** "No [objects] found for this [search/filter]", for example "No accounts found for this filter"
The word "found" in the second pattern makes it clear the system searched successfully but returned no results.
### Description
Descriptions are optional. Use them to explain why the space is empty or what the user can do next. Keep to a single sentence. Do not repeat or paraphrase the title.
### Action label
Action labels should be specific and direct. Write them as a verb followed by the object, for example "Add transaction" or "Clear filters". Avoid generic labels such as "Get started" that do not describe what happens next.
## Rules and guidance
### Usage
- **EmptyState** MUST include a title. Rationale: The title is the primary explanation of why the space is empty. Without it the component renders an icon with no context. [id: emptystate.title-required; testability: auto]
- **EmptyState** MUST NOT be shown while data is still being fetched. Rationale: Showing an empty state before data has arrived misleads the user into thinking no data exists. Use Skeleton Loading for in-progress fetch states. [id: emptystate.not-for-loading; testability: context]
- **EmptyState** MUST NOT be used to communicate a system error or an action that has failed. Rationale: Empty states communicate absence of data, not failure. Use a Notification with the error variant for errors that require user awareness or action. [id: emptystate.not-for-errors; testability: context]
- **EmptyState** MUST NOT be used to represent a null or missing value within a populated list or table. Rationale: A full empty state component in a cell creates disproportionate visual weight. Use a short phrase such as "None" or "Not applicable" inline instead. [id: emptystate.not-for-null-cells; testability: manual]
### Content
- **EmptyState icon** WILL always render the search-empty icon. The icon is hardcoded and cannot be changed via props. [id: emptystate.icon-fixed; testability: auto]
- If an action is provided: **EmptyState action** MUST directly address the reason for the empty state. Rationale: An action that does not help the user populate the space is disorienting. Completing it leaves the user back at the same empty state. If there is no relevant action, omit the action entirely. [id: emptystate.action-relevance; testability: manual]
- **EmptyState description** MUST NOT repeat or paraphrase the title. Rationale: The description should add new information, such as why the space is empty or what the user can do. Repeating the title wastes space and delays the user. [id: emptystate.title-no-repetition-in-description; testability: manual]
- **EmptyState icon** MUST NOT be replaced or removed. Rationale: The search-empty icon is hardcoded and cannot be changed via props. Attempting to override it by other means creates inconsistency across the platform. [id: emptystate.no-custom-icon; testability: auto]
- **EmptyState title** SHOULD follow one of the two standard patterns. "No [objects] on this [entity]" for empty datasets, or "No [objects] found for this [search/filter]" for no-results states. Rationale: Consistent patterns help users quickly identify the type of empty state and what they can do next. The word "found" in the search pattern signals that the system ran successfully but returned nothing. [id: emptystate.title-pattern; testability: manual]
- **EmptyState description** SHOULD be a single, concise sentence. Rationale: Empty states appear in limited space and must be read quickly. Multiple sentences suggest the content belongs elsewhere. [id: emptystate.description-single-sentence; testability: manual]
- If an action is provided: **EmptyState action label** SHOULD use a specific verb followed by the object, for example "Add transaction" or "Clear filters". Rationale: Generic labels such as "Get started" or "Try again" do not tell the user what will happen when they click. Specific labels set accurate expectations. [id: emptystate.action-label-verb-object; testability: manual]
## Accessibility
### In brief
Empty State is a `div` with an icon, a bold title, an optional description and an optional button. The title is text, not a heading, and nothing announces the Empty State when it appears.
### What Nebula provides
- The icon is hidden from screen readers, so the title and description carry the message.
- The action is a real button, with a visible focus ring.
### Keyboard behaviour
The Empty State is not a tab stop. When it has an action, the action is a button, reached with `Tab`.
#### Interactions
- **Tab** - Moves focus to the action button
- **Enter or Space** - Runs the action
### Accessible design
#### Say what is missing in the title, not in the icon
The icon is hidden from screen readers, so a screen reader user hears the title and description and nothing else. Write them so they stand alone: "No bills found for this filter".
WCAG: [1.1.1 Non-text Content](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html)
#### Name the action after what it does to the empty list
A button that says "Clear filters" or "Add transaction" makes sense read on its own, which is how a screen reader user meets it.
WCAG: [2.4.6 Headings and Labels](https://www.w3.org/WAI/WCAG22/Understanding/headings-and-labels.html)
### Accessible development
#### If the Empty State replaces results after a search or filter, announce that nothing was found
Empty State has no role and no live region, so a screen reader user is not told the results changed. Announce it in a status message, for example "No bills found".
WCAG: [4.1.3 Status Messages](https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html)
#### If the title needs to be a heading, add the heading yourself
The title is rendered as bold text, not as a heading element, so it does not appear in the page outline. Add a heading above the Empty State where the page needs one.
WCAG: [1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html)
#### If the action removes the Empty State, move focus to the refreshed content
The action button leaves the page with the Empty State, and focus is lost. Send it to the results or the filter instead.
WCAG: [2.4.3 Focus Order](https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html)
### Accessibility testing status
Reviewed on 9 October 2026 against v17.0.1.
| Test | Status | Tested with |
| --- | --- | --- |
| Default state | Tested | axe-core via the Storybook test runner (CI) |
| Advanced states | Partially tested | axe-core via the Storybook test runner (CI) |
| Keyboard navigation | Partially tested | keyboard (reported by the owner) |
| Screen reader | Partially tested | VoiceOver with Chrome (reported by the owner) |
What each test covers:
- Default state: The component as it first appears, before anything is clicked or changed.
- Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering.
- Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring.
- Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state.
What these statuses mean:
- Tested: Every check in this row passed in an automated run that repeats on every pull request.
- Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change.
- Partially tested: Some checks in this row were run once and passed; the rest are still to do.
- Issue found: A check in this row failed. The ticket is shown beside the status.
- Not tested: No check in this row has been run yet.
- Not applicable: This row does not apply to the component, for the reason given beside it.
The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used.
## Properties
### EmptyState
- `title` (type `string`, required). Text explaining that there is nothing to show. Rendered as bold text, not as a heading element.
- `description` (type `string`, optional). Supporting text shown below the title, for example how to resolve the empty state.
- `action` (type `EmptyStateAction`, optional). Renders a secondary button below the message, for a next step such as clearing filters. Takes a `label` for the button text and an `onClick` handler.
## Styles
Reference only; you do not need this to use the component. This is the CSS its styled elements use, with theme tokens shown by name. `${...}` is an interpolation (a theme token, a mixin or another value from the code). A `&[prop]` or `@when ` block holds CSS that applies only under that condition; these are notation, not valid CSS.
### title
```css
font-weight: ${fontWeights.bold};
font-size: ${responsiveFontSizes.level4[0]};
${mediaQueries.smallAndUp} {
font-size: ${responsiveFontSizes.level4[1]};
}
```
### description
```css
font-size: ${responsiveFontSizes.small[1]};
margin: 0;
```
### container
```css
display: flex;
flex-direction: column;
align-items: center;
gap: ${space[1]};
```
### empy-state-container
```css
padding: ${space[12]} ${space[4]};
background-color: ${colors.canvasMuted};
border-radius: ${radii.standard};
gap: ${space[4]};
text-align: center;
```