Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Empty state patterns for when there's no content to display. State-driven feedback with semantic HTML.
Empty states show feedback when a container has no content to display. They're a state of the container, not a separate component.
Key concepts:
data-state="empty" on the container<output data-empty data-feedback="message" role="status"> for semantic, accessible feedback<output> element is the correct semantic choice — it represents the result of an action (query returned 0 items).content classSee also: Feedback States for the unified pattern covering empty, loading, and error states.
An empty state is fundamentally feedback about a container's current status. The pattern uses:
data-state="empty" when content is absent.content class that gets hidden via CSS when state is "empty"<output data-empty data-feedback="message" role="status"> to show the feedback/* Hide feedback by default */output[data-empty] { display: none;} /* Hide content when empty, show feedback */[data-state="empty"] > .content { display: none;} [data-state="empty"] > [data-empty] { display: flex; flex-direction: column; align-items: center; gap: var(--size-m); padding: var(--size-xl); text-align: center;}
A minimal empty state with an icon, heading, and description. The container shows where messages would appear, with feedback when none exist.
<section class="messages" data-state="empty"> <h2 class="visually-hidden">Messages</h2> <!-- Populated content --> <ul class="content"> <!-- Message items go here --> </ul> <!-- Empty state feedback --> <output data-empty data-feedback="message" role="status"> <icon-wc name="inbox"></icon-wc> <h3>No messages yet</h3> <p>When you receive messages, they will appear here.</p> </output></section>
An empty state with a call-to-action button inside the output. Use this when users can immediately add content.
<section class="documents" data-state="empty"> <h2 class="visually-hidden">Documents</h2> <!-- Populated content --> <ul class="content"> <!-- Document items go here --> </ul> <!-- Empty state feedback with action --> <output data-empty data-feedback="message" role="status"> <icon-wc name="file-text"></icon-wc> <h3>No documents</h3> <p>Get started by creating your first document.</p> <button type="button"> <icon-wc name="plus"></icon-wc> Create document </button> </output></section>
An empty state with space for a custom illustration or image. Use this for feature-level empty states where visual appeal matters.
<section class="projects" data-state="empty"> <h2 class="visually-hidden">Projects</h2> <!-- Populated content --> <ul class="content"> <!-- Project items go here --> </ul> <!-- Empty state feedback with illustration --> <output data-empty data-feedback="message" role="status"> <!-- Decorative SVG uses currentColor for theming --> <svg aria-hidden="true" width="192" height="128" viewBox="0 0 192 128" fill="none"> <rect x="32" y="36" width="128" height="80" rx="6" stroke="currentColor" stroke-width="2" opacity="0.25"/> <path d="M32 42c0-3.3 2.7-6 6-6h36l8 12h72c3.3 0 6 2.7 6 6" stroke="currentColor" stroke-width="2" opacity="0.4"/> <!-- Dashed circle + plus icon hint --> <circle cx="96" cy="82" r="16" stroke="currentColor" stroke-width="2" stroke-dasharray="4 3" opacity="0.2"/> <path d="M90 82h12M96 76v12" stroke="currentColor" stroke-width="2" stroke-linecap="round" opacity="0.3"/> </svg> <h3>No projects found</h3> <p>You haven't created any projects yet.</p> </output></section>
Use appropriate elements within the output for each content type:
| Content Type | Element | Purpose |
|---|---|---|
| Status message | <output role="status"> |
Semantic result of an action, announced by screen readers |
| Heading | <h3> or appropriate level |
Describes the empty state |
| Description | <p> |
Explains what will appear or why it's empty |
| Action | <button> or <a> |
Optional CTA to add content |
<output> element: Represents the result of an action.
Screen readers announce its content naturally when it becomes visible.role="status": Creates a polite live region. When the empty
state appears via JavaScript, screen readers announce the message without interrupting..visually-hidden headings: Container headings like
<h2 class="visually-hidden">Messages</h2> provide navigation
landmarks for screen reader users even when hidden visually.<icon-wc> without a
label attribute is automatically aria-hidden="true",
preventing redundant announcements alongside the heading text.The pattern works CSS-only with server-rendered data-state="empty". For dynamic content, toggle the attribute:
Choose icons that represent the empty content type. Common icons from the Lucide set:
| Icon | Name | Use For |
|---|---|---|
inbox |
Messages, notifications | |
file-text |
Documents, notes | |
users |
People, team members | |
folder |
Files, projects | |
search |
Search results | |
image |
Photos, media | |
calendar |
Events, schedules | |
shopping-cart |
Cart, orders |
Unified empty, loading, and error states
404, 500, and maintenance pages
Loading placeholders for content