Placeholders
Empty state components displayed when no content is available in a view. Use to guide users toward a relevant action when a list, table, or page has no data. Don't leave empty states blank.
FigmaDescription
Displays a structured empty state when a list, table, or content region has no data to show. Combines an icon or illustration, a heading, supporting text, and an optional action to help users understand the situation and take a next step.
Inside empty list views, data tables with no rows, search results with no matches, filtered views that return zero items, and dashboard widgets awaiting data. Common in Open Point stakeholder lists, consultation participant tables, and Social Point activity feeds.
An empty view with no explanation causes confusion and erodes trust — especially in government software used by non-technical staff. Placeholders make the system feel responsive and intentional, and reduce support requests by directing users toward the correct action.
Anatomy
| Part | Required? | Notes |
|---|---|---|
| Container | Required | Full-width block centred within its parent region. Sets padding and vertical rhythm for all child elements. |
| Icon or illustration | Recommended | A single wa-icon or a decorative illustration that represents the empty category (e.g. a person outline for an empty stakeholder list). Sized at 48px by default. |
| Heading | Required | One short sentence naming what is empty. Uses --op-text-lg, --op-font-headline, --op-color-text-primary. Example: 'No stakeholders added yet'. |
| Supporting text | Recommended | One or two sentences explaining why the state exists or what to do next. Uses --op-text-sm, --op-font-body, --op-color-text-secondary. |
| Primary action | Optional | A single wa-button (variant='primary') pointing to the most likely next step. Only include when a direct action resolves the empty state. |
| Secondary action | Optional | A text link or ghost button for a secondary path, such as 'Learn more' or 'Import from CSV'. Appears below the primary action. |
Variants
Default
Generic empty state with icon, heading, body text, and optional action.
Use for most empty lists and tables where the user may or may not be able to take action immediately.
No results
Communicates that a search or filter returned zero matches, not that data is absent.
Use when a search query or active filter is responsible for the empty state. Include a suggestion to clear filters or adjust the search term.
No access
Communicates that content exists but the user does not have permission to view it.
Use when authorisation is the reason for the empty view. Do not use the default variant — the messaging and action differ.
Error
Communicates that data failed to load due to a system error.
Use when a fetch or load operation fails. Pair with a retry action. Uses --op-color-status-error-bg for the container background.
First use
Communicates a genuinely empty dataset that the user will populate over time.
Use for brand-new accounts or empty modules. Can include a more instructional tone and a prominent primary action.
States
| State | Behaviour |
|---|---|
| Static | Default render — icon, heading, text, and optional action are visible. No interactive states on the container itself. |
| Loading (transitional) | Before the empty state is confirmed, show a wa-spinner or skeleton. Only replace with the placeholder once the data fetch has resolved with an empty result. |
| Action hover | The embedded wa-button follows its own hover state using --op-color-interactive-hover. The placeholder container itself does not change. |
Usage guidelines
When to use
- A list, table, or feed has zero items to display.
- A search or filter returns no matching results.
- A dashboard widget has no data for the selected date range.
- A new account or module has not yet been populated.
When not to use
- When content is still loading — show a spinner or skeleton loader instead, then transition to the placeholder only once the empty result is confirmed.
- When the empty state is a transient error that will resolve automatically — use a toast or inline alert instead.
- As a full-page error state for 404 or 500 errors — use a dedicated error page layout instead.
Do / Don't
Do
Write heading text that names the specific thing that is empty: 'No consultations found' rather than 'Nothing here'.
Don't
Use vague headings like 'No data' or 'Empty' that give the user no context.
Do
Include a primary action only when it directly resolves the empty state (e.g. 'Add stakeholder', 'Create consultation').
Don't
Add a primary action that navigates away to an unrelated part of the product — this increases cognitive load.
Do
For no-results states, show what filters or search terms are active and offer a 'Clear filters' action.
Don't
Show the same generic placeholder for both a genuinely empty list and a filtered-empty list — users need different guidance.
Do
Keep supporting text to one or two sentences. Write in plain language accessible to non-technical government staff.
Don't
Include technical error codes or jargon in the visible placeholder text.
Layout & Spacing
Container: padding --op-space-48 (48px) top and bottom, --op-space-24 (24px) left and right. Container max-width: 480px, centred with margin: 0 auto. Icon / illustration: margin-bottom --op-space-16 (16px). Heading: margin-bottom --op-space-8 (8px). Supporting text: margin-bottom --op-space-24 (24px) when an action follows; --op-space-2 when it is the last element. Action stack: gap --op-space-12 (12px) between primary and secondary actions, centred.
Tokens
| Part | Token | Value |
|---|---|---|
| Container background | --op-color-bg-primary | Transparent or surface-level background — matches the parent panel or page. |
| Container background (error variant) | --op-color-status-error-bg | Subtle tinted background to signal a system error state. |
| Icon | --op-color-text-secondary | Muted icon colour so it does not compete with the heading. |
| Heading text | --op-color-text-primary | Full contrast heading. |
| Supporting text | --op-color-text-secondary | Secondary text colour for the descriptive copy. |
| Heading font | --op-font-headline | ABC Otto. Size: --op-text-lg. |
| Body font | --op-font-body | ABC Social. Size: --op-text-sm. |
Engineering notes
- The placeholder is a layout pattern, not a single web component. Compose it from a div container, wa-icon, heading element (h2 or h3 depending on page hierarchy), p, and wa-button.
- Always wait for the data fetch to resolve before rendering the placeholder. Avoid a flash of the empty state while data is loading — show wa-spinner first.
- Use the correct heading level (h2 or h3) based on the surrounding document outline. Do not use h1 inside a placeholder within a page that already has an h1.
- For the no-results variant, surface the active search term or filter names in the supporting text so users know exactly what returned no results.
- When the error variant is used, log the underlying error to your monitoring tool but do not expose stack traces or error codes in the visible UI.
- If the placeholder appears inside a wa-dialog or panel, reduce padding to --op-space-32 (32px) top and bottom to suit the smaller container.
Keyboard interaction
| Key | Action |
|---|---|
| Tab | Moves focus to the primary action button, then the secondary action (if present). The placeholder container itself is not focusable. |
| Enter | Activates the focused action button. |
| Space | Activates the focused action button. |
Why it matters
Government users include people with low digital literacy, cognitive disabilities, and assistive technology users. An empty state that is not announced to screen readers leaves these users uncertain whether the page has loaded correctly, which can cause repeated refresh attempts and loss of trust in the system.
Focus
The placeholder container has no focus state of its own. Focus passes through to interactive children (wa-button) in DOM order. When a placeholder replaces a loading spinner, move focus to the placeholder heading using programmatic focus (tabindex="-1" on the heading element, then heading.focus()) so screen reader users are informed of the state change without a full page reload. Focus ring on buttons: 3px solid --op-color-interactive-focus, offset 2px.
ARIA
| Role or attribute | When to use | Example |
|---|---|---|
aria-live="polite" | Apply to the placeholder container region when it is injected into the DOM after a data fetch. This announces the empty state to screen readers without interrupting the user. | <div class="placeholder" aria-live="polite"> |
role="status" | Can be used instead of or alongside aria-live='polite' on the container to convey a status message to assistive technology. | <div class="placeholder" role="status"> |
aria-hidden="true" on decorative icon | Mark the wa-icon as aria-hidden='true' when it is purely decorative and the heading already describes the empty state. | <wa-icon name="person-outline" aria-hidden="true"></wa-icon> |
tabindex="-1" on heading | Add when programmatically shifting focus to the placeholder heading after a state transition, to avoid adding it to the natural tab order. | <h2 tabindex="-1" class="placeholder__heading">No stakeholders added yet</h2> |
Contrast
Heading (--op-color-text-primary on --op-color-bg-primary): meets WCAG AA at all text sizes. Supporting text (--op-color-text-secondary on --op-color-bg-primary): verify at --op-text-sm; must meet 4.5:1. Icon (--op-color-text-secondary on --op-color-bg-primary): decorative — no contrast requirement if aria-hidden="true". Error variant background (--op-color-status-error-bg): ensure heading and body text tokens still meet 4.5:1 against this tinted background.
Touch targets
The placeholder container itself is not interactive. All embedded wa-button elements must maintain a minimum touch target of 44x44px. Do not reduce button size to fit a narrow placeholder container — use the full-width button option instead.
Things to avoid
- Do not use colour alone to distinguish the error variant from the default variant — also change the icon and heading text.
- Do not auto-dismiss or replace the placeholder with a spinner loop — this disorients screen reader users.
- Do not place placeholder text in a visually hidden element while showing only an illustration — the text must be visible, not just announced.