Skip to content

EmptyState

<caos-empty-state>

what to show when there is nothing yet; never a bare blank region

May be placed on a record page, an app’s home page, a page inside an app and a step of a guided flow.

Name Attribute Description Type Default Required
title title, markup only The heading — one short line naming what is missing (“No orders yet”), not a sentence. Empty or unset and the heading is not drawn at all. Note this is also the global HTML title attribute, so the browser shows it as a tooltip too. text none declared Optional

This piece raises no events.

Name What goes in it
the default slot The description under the heading: a sentence or two saying what would put something here. This is the half that does the work — a heading alone only restates the blank region.
icon A glyph above the heading, replacing the default. Left alone it draws a neutral inbox mark, sized 40px and drawn in the muted ink. Hidden from assistive technology either way.
action The one thing to do next — usually a single Button. The row is not drawn at all when the slot is empty, so an empty state with nothing to offer does not leave a gap where a control would be.
Design token What it controls
--caos-color-text The heading ink.
--caos-color-text-muted The icon and the description ink.
--caos-font-display The face of the heading.
--caos-space-3 The gaps between the icon, the heading and the description.
--caos-space-2 The gap between actions, and the space above the action row.
--caos-space-5 The padding across the column.
--caos-space-7 The padding above and below — what gives the state its room.

Nothing else drives this piece by calling it.

Nothing on this piece can be read back.

Part Which piece of it When it is there
::part(container) the centred block Always
::part(icon) the glyph above the title Always
::part(title) the headline Always
::part(description) the explanatory text Always
::part(actions) the row holding the slotted action Always

A region has nothing to show and would otherwise be blank. It is never optional: a bare empty area reads as a screen that has broken or not finished, and both of those are wrong more often than they are right.

  • SkeletonBlock — The region is not empty — it is still loading, and something is on its way.
  • ErrorPanel — The region is empty because something FAILED. That needs the code, the correlation id and a way forward.
  • AccessDeniedPanel — The region is empty because this person may not see what is in it. That has to say which grant is missing without leaking what was hidden — an empty state would be a lie.
  • NoticeBanner — There IS content, and something needs saying about it. A notice sits above the content; an empty state stands in for it.

The icon is hidden from assistive technology, so nothing meaningless is announced, and the heading and description are read in order as ordinary text. What is left to the author is the wording, and it is the whole job: the heading names what is missing, the description says what would put something here, and the action is the one thing to do about it. The piece draws no heading level of its own, so a surface that needs this in its heading outline has to supply one. It is also not a live region — replacing loaded content with an empty state after a filter change announces nothing unless the region around it says so.

The list has never had anything in it: the heading names what is missing, the description says what would put something here, and the action is the thing that does it.

{
"id": "example",
"section": "Nothing yet",
"columns": 1,
"items": [
{
"id": "empty_state_1",
"type": "component",
"key": "empty_state",
"inputs": {
"title": "No orders yet"
},
"children": [
"Orders raised for this account will be listed here, newest first. Start one from a request, or from a blank order.",
{
"id": "button_2",
"type": "component",
"key": "button",
"inputs": {
"variant": "primary"
},
"slot": "action",
"children": [
"New order"
]
}
]
}
]
}
<caos-empty-state title="No orders yet">
Orders raised for this account will be listed here, newest first. Start one from a request, or from a blank order.
<caos-button slot="action" variant="primary">New order</caos-button>
</caos-empty-state>

The same region, empty for the opposite reason: there ARE orders, the filter just excluded all of them — so the way out is to widen the filter, not to create a record.

{
"id": "example",
"section": "Nothing matches",
"columns": 1,
"items": [
{
"id": "empty_state_1",
"type": "component",
"key": "empty_state",
"inputs": {
"title": "No orders match this filter"
},
"children": [
"Twelve orders exist on this account, but none of them reached the Approved stage in the last 30 days.",
{
"id": "button_2",
"type": "component",
"key": "button",
"inputs": {
"variant": "secondary"
},
"slot": "action",
"children": [
"Clear filters"
]
}
]
}
]
}
<caos-empty-state title="No orders match this filter">
Twelve orders exist on this account, but none of them reached the Approved stage in the last 30 days.
<caos-button slot="action" variant="secondary">Clear filters</caos-button>
</caos-empty-state>