States — empty, loading, error
State block — what a data container shows before its data, while it comes, and when it fails: Empty, Skeleton and Alert composed as one component, the three states the product kit lists for every data container.
Beta · Shells and patterns · packages/css/src/components/states.css
.pui-state-block (--card) · __skeleton-row __error — Empty, Skeleton and Alert as one block. Never a Spinner for a container.
Sizes across, states down.
What it is for
Section titled “What it is for”- State: empty · loading · error; Context: table · card
- The Empty and the Alert are exposed — their title and description say what is missing or what failed and what to do; the Retry is exposed, Has action hides it (an error the user cannot retry)
- The block replaces the container’s body, under its own header (the example); it takes the container’s width
Use when any table, list, card or panel that loads data — every data container carries all three.
Do not use when the whole page failed (a page-level Alert or an error page); a form’s field error (Field); a control’s own busy state (Spinner in a Button).
Relates to Empty, Skeleton, Alert, Spinner, Data table, Table, Card, Button - secondary, Toast - status.
Anti-patterns a spinner where a skeleton belongs · an error with no way to retry when a retry would work · ‘Error’ as a title · a skeleton that does not match the rows it stands in for · a fill changed on the instance.
Keywords: empty state, loading state, error state, skeleton, data container
Empty + Skeleton + Alert as one trio
Figma — page States — empty, loading, error: State block (6 variants)
The markup — copy this
Section titled “The markup — copy this”The class, the parts, and nothing that is not in the package. The same block opens states.css.
<div class="pui-state-block"> …a .pui-empty… </div><div class="pui-state-block"><div class="pui-state-block__skeleton-row"><span class="pui-skeleton pui-skeleton--circle pui-skeleton--24"></span> <span class="pui-skeleton pui-skeleton--line" style="width: 160px"></span><span class="pui-skeleton pui-skeleton--line" style="width: 80px"></span> <span class="pui-skeleton pui-skeleton--line"></span></div> ×5 </div><div class="pui-state-block"><div class="pui-state-block__error"><div class="pui-alert pui-alert--danger pui-alert--outline">…</div> <button class="pui-button pui-button--secondary pui-button--small">Retry</button></div></div>The modifiers
Section titled “The modifiers”| What it is | Note | |
|---|---|---|
| Context | (none) table · --card (368, the error stacks) | — |
Accessibility — measured, not assumed
Section titled “Accessibility — measured, not assumed”Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.
Contrast
Section titled “Contrast”| ink on ground | reads | floor | the matrix calls it | |
|---|---|---|---|---|
#544d4d on #ffffff |
8.24 | 4.5 | text.muted on surface.page (promised) | ok |
#5b0e0d on #fce9e8 |
11.86 | 4.5 | Danger badge (promised) | ok |
#0b0202 on #ffffff |
20.51 | 4.5 | text.default on surface.page (promised) | ok |
#ffffff on #0b0202 |
20.51 | 4.5 | Primary button label (promised) | ok |
4 distinct pair(s) rendered, 0 that axe calls a violation.
The focus ring
Section titled “The focus ring”4 of 4 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured.
Keyboard
Section titled “Keyboard”Nothing in this component claims an ARIA role with a keyboard contract; its controls are ordinary ones and the browser already gives them their keys.
Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).
Pepper UI v1.5.0
The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.