> ## Oymyakon DS index
> Fetch the full index at: https://super-dollop-pzmo65r.pages.github.io/llms.txt
> HTML preview: https://super-dollop-pzmo65r.pages.github.io/status-item.html
> Source: specs/components/status-item/status-item.md, specs/components/status-item/status-item-a11y.md, specs/components/status-item/capabilities.json

---

# StatusItem · Oymyakon DS 3

> A centred status block — an illustration or an icon, a title, a subtitle and an optional action —
> that tells the user how an operation ended or what state a screen or section is in.

**Version:** 3.2.2 · **Status:** Ready · **Figma:** `[StatusItem] 3.2` (main components file `7vdl5YkZFDWvh9QvSmydsH`, page `8384:570`, set `8441:6573`, key `d73a3382…`; public master `37168:138571`; Building blocks section `8467:1389`; Update frame `11563:3500`)

---

## 1. Description

StatusItem reports a result or a state in one centred block: "Payment completed", "Something went
wrong", "The connection is lost", "Nothing found". The top of the block carries the picture of
the state — a full illustration in the **Standard** size, a coloured Squircle with an icon in the
**Compact** size — and the bottom carries the title, the subtitle and, when the user can do
something about it, one secondary button ("Try again").

Five types share one layout and differ only in the picture: **success**, **error**, **no
connection**, **empty state** and **warning**. A sixth, **custom**, keeps the layout and hands the
top slot to the host. While the data that decides the state loads, a **Skeleton** replaces the
whole block.

StatusItem is the content of a screen, a section or a bottom sheet — it stays until the state
changes. It is not a transient message (that is Notification or Snackbar) and not a page-level
banner (that is HeaderAlert).

### Platform implementations

| Platform | Entry point | Version | Notes |
|---|---|---|---|
| iOS | `DsStatusItem` (`DS3`) · `DsCustomStandardStatusItem` / `DsCustomCompactStatusItem` | Figma 3.1 | `init(preset:size:titleText:subtitleText:buttonTitle:buttonAction:)` — all six arguments required; `DsStatusItemPreset` (`success` · `error` · `noConnection` · `emptyState` · `warning`) × `DsStatusItemSize` (`standard` · `compact`); `isArtworkHidden` / `isButtonHidden`; skeleton = `.isLoading(true)` |
| Android | `DsStatusItem` (`…ds.v3.status_item`) · `DsCustomStatusItem` | Figma 3.1 | `DsStatusItem.Preset` joins Type × Compact (`StandardSuccess` / `CompactSuccess` … `StandardWarning` / `CompactWarning`); `showIllustration`; `title` / `subtitle` optional (`DsString?`, the preset's copy by default); the button shows when `buttonTitle` + `onClick` are passed; skeleton = `isLoading = true` |
| Flutter | — | — | Not shipped |
| Web | `src/shared/shared.css` (`.status-item`) | 3.2.0 | Reference implementation, this repo; preview page `src/status-item.njk` |

Both platform builds also host StatusItem as the content of the "Status" bottom sheet
(`DsBottomSheet` with a `DsStatusItem` inside — no dedicated composable).

**3.1 → 3.2:** the Standard illustration grows from 220 × 220 to 240 × 240 (owner, 2026-10-21).
Both platforms ship 3.1, so they still draw it at 220 until they update.

---

## 2. Anatomy

```
StatusItem (.status-item)                  fills the host's width · vertical stack, centred · no padding, no gap
├── TopSlot (.status-item__top)            optional (TopSlot boolean, on) · height hug · s16 top, s8 bottom (Compact 0)
│   ├── Illustration                       Standard — 240 × 240, the type's illustration
│   └── Squircle                           Compact — M (s48), the type's icon and colours
└── BottomSlot (.status-item__bottom)      required · height hug · s16 top/bottom, s20 left/right · gap s24 / s16
    ├── TextContainer (.status-item__text) centred · gap s4
    │   ├── Title (.status-item__title)    optional (on) · Heading 2 (Standard) / Heading 4 (Compact)
    │   └── Subtitle (.status-item__subtitle) optional (on) · Main Body
    └── Button                             optional (on) · Secondary · L (Standard) / S (Compact)
```

| Part | Required | Description |
|---|---|---|
| **TopSlot** | Optional — on by default | The picture of the state, centred, with `s16` above; below it `s8` in Standard and none in Compact. Its height **hugs** the picture — no fixed height. Standard: the type's illustration at 240 × 240. Compact: a Squircle M with the type's icon. Custom: any DS content the host places |
| **Illustration** | Standard only | One of five DS illustrations, a square vector (`illu/status/`). Decorative — the title and subtitle carry the meaning |
| **Squircle** | Compact only | `Custom-Squircle` (s48, radius `s16`, no border) tinted per type for success / error / no connection / warning; an `M-Squircle` with a border for empty state (§ 6) |
| **TextContainer** | Required while a line is on | The title and the subtitle, centred, wrapping. With both lines off the web omits it — an empty container would still take the BottomSlot gap, and the a11y spec has no text element then |
| **Title** | Optional — on by default | What happened, in one short line: "Payment completed", "The connection is lost" |
| **Subtitle** | Optional — on by default | Why, or what to do next. The no-connection type stages it off — its title says it all |
| **Button** | Optional — on by default | One action — [Button](https://super-dollop-pzmo65r.pages.github.io/button.md) `Secondary` (Surface/OnWhite), `L` (s56) in Standard, `S` (s40) in Compact; hugs its label, centred |

- **Both slots hug their content vertically.** Neither the TopSlot nor the BottomSlot has a fixed
  or minimum height: each is its padding plus what is inside it. The block's height follows — a
  longer title or subtitle wraps and grows the BottomSlot, a part switched off (the subtitle, the
  button, the TopSlot itself) takes its space with it. The heights quoted in this spec (428 / 196,
  skeleton 408 / 180) are the one-line demo copy at 100%, not sizes to set.
- The block has no surface of its own: no fill, no border, no radius. It sits on the host's
  background.
- There is one action at most. A second action belongs to the host (a sheet's footer, a screen's
  bottom bar), not to StatusItem.

### RTL layout

The Figma set stages no RTL axis — every part is centred, so the block reads the same in both
directions. The text lines centre in both directions, the button follows its own RTL contract
(its slots mirror), and the illustrations and icons do not mirror (none of them is directional).

---

## 3. Variants and sizes

| Axis | Values | Default | Web mechanism |
|---|---|---|---|
| **Type** | `success` · `error` · `no connection` · `empty state` · `warning` · `skeleton` | `success` | `.status-item--{type}` (`--success`, `--error`, `--no-connection`, `--empty-state`, `--warning`) — the picture in the TopSlot; on Compact it re-points the nested Squircle's colours (§ 6). `skeleton` is a state on the web and on both platforms (`.status-item--skeleton`, § 4), not a type |
| **Compact** | `false` · `true` | `false` | The bare `.status-item` is Standard; `.status-item--compact` swaps the illustration for the Squircle, the title style, the button size and the BottomSlot gap |
| **TopSlot** | `true` · `false` | `true` | The TopSlot element is present or omitted |
| **Title** · **Subtitle** · **Button** | `true` · `false` | `true` | The element is present or omitted (BottomSlot booleans) |
| **Custom** | Standard · Compact | — | `CustomStatusItem` (its own Figma set, `37170:139847`) — the same layout with an open top slot. Host-composed on the web: no CSS of its own — the host fills `.status-item__top` with DS content; `.status-item--custom` is a marker class only (no rule), and the preview stages no custom specimen |

| Type | Standard illustration | Compact icon | Compact Squircle |
|---|---|---|---|
| **success** | `indrive-success-state-color` | `check` | Custom-Squircle, `Pastel/Green 1` + `TextAndIcon/Success` |
| **error** | `indrive-error-state-color` | `close` | Custom-Squircle, `Pastel/Red 1` + `TextAndIcon/Error` |
| **no connection** | `indrive-no-internet-color` | `internet-off` | Custom-Squircle, `Pastel/Red 1` + `TextAndIcon/Error` |
| **empty state** | `indrive-empty-state-color` | `search` | M-Squircle, `Surface/OnWhite` + `Border/Default` border — the Squircle Standard state with the border on |
| **warning** | `indrive-warning-state-color` | `warning-tr-outline` | Custom-Squircle, `Pastel/Orange 1` + `TextAndIcon/Warning` |

| Size | TopSlot | Title | Button | BottomSlot gap |
|---|---|---|---|---|
| **Standard** (default) | Illustration 240 × 240 | Heading 2 | `L` — s56 | `s24` |
| **Compact** | Squircle M — s48 | Heading 4 | `S` — s40 | `s16` |

- Use **Standard** when StatusItem is the main content of a screen or a tall sheet; **Compact** in
  a smaller area — a section of a screen, a card, a short sheet.
- **CustomStatusItem** differs between its sizes in Figma: the Compact set wraps the custom
  content in the TopSlot (`s16` above and below), the Standard set places it directly, with no
  padding. Flagged for the Figma side.
- The platforms bind the Figma 3.1 property names `Illustration` and `Button`; the 3.2 set exposes
  `TopSlot` on the root and `Title` / `Subtitle` / `Button` on the BottomSlot. Recorded for the
  next bridge sync.

---

## 4. States

| State | What shows |
|---|---|
| **Standard** | The type's picture, the title, the subtitle and the button, as configured |
| **Skeleton** | The block's loading shape: a square shape in the TopSlot (Standard 240 × 240, Compact a Squircle M skeleton), one text bar and a button-shaped bar; the DS shimmer runs across them |

- StatusItem itself is not interactive: no Pressed, Hover, Focused or Disabled state on the block.
  The button carries its own states ([Button § 4](https://super-dollop-pzmo65r.pages.github.io/button.md)).
- The type is content, not a state: an error StatusItem does not change into a success one; the
  host replaces it.
- Skeleton geometry (Figma): Standard — TopSlot shape 240 × 240 (radius `s24`) with the slot's own
  paddings (`s16` above, `s8` below); a text bar 200 × 44 inside `s2` vertical padding (48 in all, radius `s8`) and a button bar
  120 × 56 (radius `s20`), `s8` apart, `s16` above and below (block height 408). Compact — a
  Squircle M skeleton with `s16` above and no gap below it, then a text bar 200 × 16 inside `s2` padding (radius `s6` — the Main Body
  text-preset geometry) and a button bar 120 × 40, `s8` apart, `s16` around the BottomSlot's content and `s16` of the block's own padding below (block height 180).
- Web: every skeleton shape is a bare Skeleton (`.skeleton`, the Custom preset) sized by the block —
  `.status-item__skeleton-art`, `.status-item__skeleton-text`, `.status-item__skeleton-button` — and
  the Compact square is the Squircle's own `.squircle--skeleton`. Figma nests a Button in its
  Skeleton state for the button bar; the web does not, because Button's skeleton has a fixed width
  (72 / 64) and StatusItem's bar is 120 — a bare shape needs no override of a nested component.
- The Compact skeleton is assembled without the TopSlot / BottomSlot instances in Figma (a flat stack — the Squircle and the BottomSlot frame with no gap) and nests `[Squircle] 3.5`, one version ahead of the repo's Squircle
  spec (3.4.3). Flagged for the Figma side and for the Squircle spec.

---

## 5. Animation and behavior

Reference: [`motion-rules.md`](https://super-dollop-pzmo65r.pages.github.io/motion.md).

| Event | Pattern | Token | Notes |
|---|---|---|---|
| Button press | `pushButton` | `--component-push-button-*` | The button's own motion ([Button § 5](https://super-dollop-pzmo65r.pages.github.io/button.md)); StatusItem adds none |
| Skeleton shimmer | `Patterns/Shimmer` | `var(--pattern-shimmer)` | The DS skeleton sweep, the same as the Skeleton component |
| Skeleton → Standard | — | — | The host swaps the skeleton for the content; StatusItem declares no transition of its own |
| Type change | — | — | The host replaces the block; no cross-fade between types |

- The illustrations are static — no Lottie, no loop.
- Reduced motion: `--pattern-shimmer` and the push tokens resolve to `none` / zero at the token, so
  the skeleton holds still and the button presses without scale; nothing else moves.

---

## 6. Color tokens

| Element | Token | Notes |
|---|---|---|
| Block background | — | None — the host's background shows through |
| Title | `var(--text-and-icon-primary)` | Set by Update 3.1 (the DS 3.0 colours) |
| Subtitle | `var(--text-and-icon-primary)` | Set by Update 3.1 — the same token as the title |
| Squircle fill — success | `var(--pastel-green1)` | Compact |
| Squircle icon — success | `var(--text-and-icon-success)` | |
| Squircle fill — error · no connection | `var(--pastel-red1)` | |
| Squircle icon — error · no connection | `var(--text-and-icon-error)` | |
| Squircle fill — warning | `var(--pastel-orange1)` | |
| Squircle icon — warning | `var(--text-and-icon-warning)` | |
| Squircle fill — empty state | `var(--surface-on-white)` | The M-Squircle's Standard fill (Figma re-bound from `Background/BaseGrey`, owner 2026-10-21) |
| Squircle border — empty state | `var(--border-default)` | 1dp inside |
| Squircle icon — empty state | `var(--text-and-icon-primary)` | The M-Squircle's default content colour |
| Illustrations | — | Fixed artwork colours, the same in Day and Night |
| Button | Button `Secondary` tokens | `var(--surface-on-white)` fill, `var(--text-and-icon-primary)` label — see [Button § 6](https://super-dollop-pzmo65r.pages.github.io/button.md) |
| Skeleton fill · wave | `var(--skeleton-on-white)` · `var(--skeleton-wave)` | |

- Contrast, text: `TextAndIcon/Primary` on `Background/Primary` ≈ 17.8:1 Day / ≈ 17.0:1 Night.
- Contrast, Compact icon on its Pastel fill (fill composited over `Background/Primary`): success
  ≈ 3.3:1 Day / ≈ 5.3:1 Night · error and no connection ≈ 3.4:1 / ≈ 4.1:1 · **warning ≈ 2.7:1 Day**
  / ≈ 4.9:1 Night. The warning icon falls below the 3:1 non-text minimum in Day — the icon is
  decorative (the title carries the state), the DS-wide pastel fact recorded on Squircle. Flagged
  for the owner.
- The illustrations do not adapt to Night: the artwork carries its own black outlines and coloured
  backing shapes, so it reads on both themes.

---

## 7. Typography

| Element | Size | Style | Tokens | Scaling |
|---|---|---|---|---|
| Title | Standard | Heading/Heading 2 — 24/28, PP Agrandir Bold | `var(--text-heading-heading2-*)` | `cap130` (Figma binds `SP/130% ratio`) |
| Title | Compact | Heading/Heading 4 — 17/20, Suisse Intl Semibold | `var(--text-heading-heading4-*)` | `full` (Figma binds `SP/150% ratio`) |
| Subtitle | both | Body/Main Body — 16/20, Suisse Intl Book | `var(--text-body-main-body-*)` | `full` |
| Button label | both | The button size's own style | — | [Button § 7](https://super-dollop-pzmo65r.pages.github.io/button.md) |

- Both lines centre and wrap to as many lines as the text needs; Figma sets no line limit and no
  truncation.
- PP Agrandir on the Standard title follows the typeface rule (Heading 2 is a large heading).

---

## 8. Spacing

Figma binds the paddings and gaps to `SP/100% ratio` (they keep their size while the text scales)
and the TextContainer gap to `SP/150% ratio`. On the web every SP-bound value follows the SP mode
(100 / 130 / 150%) — the repo's spacing tokens carry no fixed-ratio set (a repo-wide deviation).

| Property | Token | Standard @100% | Compact @100% | Notes |
|---|---|---|---|---|
| Width | — | fills the host | fills the host | Figma stages the set at 360 — the phone width; the block has no width of its own |
| Block padding · gap | `var(--sp-s0)` | 0 · 0 | 0 · 0 | The slots own the spacing |
| TopSlot padding — top | `var(--sp-s16)` | 16 | 16 | Sides 0 |
| TopSlot padding — bottom | `var(--sp-s8)` / `var(--sp-s0)` | 8 | 0 | Standard `s8` (owner, 2026-10-21); Compact none — the Squircle sits directly on the BottomSlot's `s16` |
| TopSlot · BottomSlot height | — | hug | hug | No fixed or minimum height — padding plus content; the block's height is their sum |
| Illustration | `--status-item-illustration-size` | 240 × 240 | — | Square, **fixed** — does not grow with the SP mode, as in Figma (owner, 2026-10-21). A component-local length (`240px`), not an SP token: an SP token scales by definition, so it holds 240 wherever the mode is set — an inner `.sp-*` scope or `data-sp` on `<html>` (owner, 2026-10-30, replacing the earlier `s240` scale step) |
| Squircle | `var(--sp-s48)` | — | 48 | Squircle M; radius `var(--sp-s16)` (max ×130%) |
| BottomSlot padding — top / bottom | `var(--sp-s16)` | 16 | 16 | |
| BottomSlot padding — left / right | `var(--sp-s20)` | 20 | 20 | Keeps the text and the button off the host's edges — at the 360 staging width the lines wrap at 320. The skeleton variants' own BottomSlot frames carry the same `s20` |
| BottomSlot gap — text ↔ button | `var(--sp-s24)` / `var(--sp-s16)` | 24 | 16 | |
| TextContainer gap — title ↔ subtitle | `var(--sp-s4)` | 4 | 4 | Figma `SP/150% ratio` — grows with the text |
| Button height | `var(--sp-s56)` / `var(--sp-s40)` | 56 | 40 | Button `L` / `S` |
| Skeleton text bar width | `--status-item-skeleton-text-width` | 200 | 200 | A component-local value, not an SP token (owner, 2026-10-21); fixed like the illustration |
| Skeleton button bar | `var(--sp-s120)` × the button height | 120 × 56 | 120 × 40 | |
| Skeleton BottomSlot gap | `var(--sp-s8)` | 8 | 8 | |

### Owner rulings (2026-10-21, 2026-10-30)

- **Illustration 240, fixed.** It holds 240 in every SP mode — the text around it scales, the
  artwork does not, so at 150% it never fills the block's width. It is a component-local length
  (`--status-item-illustration-size: 240px`), not an SP token (2026-10-30: the `s240` scale step
  first added for it was dropped — it scaled to 312 / 360, which its only consumer must not do, and
  a `:root` pin of a scaling token fails when the SP mode is set on `<html>`).
- **Skeleton text bar 200, local.** A component-local value (`--status-item-skeleton-text-width`),
  not an SP token — the same treatment as the 240; `s196` was not adopted. Precedent:
  `--pinwheel-picker-container-max`.
- **Empty-state Squircle fill** re-bound in Figma to `Surface/OnWhite` — the Squircle Standard fill.

---

## 9. Usage context

### When to use StatusItem

- The result of an operation the user started: "Payment completed" (success), "Something went
  wrong — Try again" (error).
- A screen or a section that cannot show its content: "The connection is lost — Try again" (no
  connection), "Nothing found" for an empty search or an empty list (empty state).
- A condition the user should know before going on (warning).
- The content of the "Status" bottom sheet — Compact in a short sheet, Standard in a tall one.
- A loading placeholder for any of the above, while the state is not yet known (Skeleton).

### When not to use

- For a short-lived confirmation over the current content — use
  [Notification](https://super-dollop-pzmo65r.pages.github.io/notification.md) or [Snackbar](https://super-dollop-pzmo65r.pages.github.io/snackbar.md).
- For a persistent page-level banner above the content — use
  [HeaderAlert](https://super-dollop-pzmo65r.pages.github.io/header-alert.md).
- With two or more actions — StatusItem carries one; the others go to the host.
- Inside a list row or next to other content in a row — StatusItem is a centred block that takes
  the area's width.
- With a picture from outside the DS on a standard type — a custom picture uses CustomStatusItem.

### Related components and patterns

- [Squircle](https://super-dollop-pzmo65r.pages.github.io/squircle.md) — the Compact picture.
- [Button](https://super-dollop-pzmo65r.pages.github.io/button.md) — the action.
- [Skeleton](https://super-dollop-pzmo65r.pages.github.io/skeleton.md) — the loading shapes and the shimmer.
- **Empty State** — a pattern (a screen-level composition), not a component; StatusItem's
  `empty state` type is the block such a screen shows. The pattern lives in the composition layer.

---

## 10. Accessibility

Brief summary. Full spec: [`status-item-a11y.md`](https://super-dollop-pzmo65r.pages.github.io/status-item.md).

- Two stops: the **text** and the **button** (a third only for a Custom top slot whose content
  carries information). The illustration and the Squircle are decorative — hidden from assistive tech; colour is never the only signal (error and no connection
  share a red Squircle, the text tells them apart).
- The title and the subtitle merge into **one element with the heading trait**, title first — one
  swipe for the state, and a heading to jump to on a full screen. On the web they stay two lines,
  the heading first.
- Announcing depends on how the block appears, and the host decides: on screen load — read in
  reading order; replacing the whole screen after an action — focus moves to the text; replacing a
  section — the text is announced once, politely, focus stays; inside a modal host the sheet owns
  focus. After "Try again", focus decides: if it is still where it was (Button Loading), the result
  is announced even when its text repeats; if the focused button is gone, focus moves to the new
  text, which reads it — never both. A Skeleton retry parks focus on the loading region first. No
  alert semantics.
- The button's label names the action ("Try again"), not the state. An unavailable action is
  omitted, not disabled.
- Skeleton: not announced and not focusable. "Loading" is the text of a separate `role="status"`
  node without `aria-busy`; `aria-busy` goes on the region being loaded.
- Text scaling: the Standard title (Heading 2) caps at 130%, below WCAG 1.4.4's 200% (the DS-wide
  heading rule); the Compact title and the subtitle scale to 150%; the illustration holds 240. The
  lines wrap without truncation.
- What is shown is what is read: any combination of the title, the subtitle and the button is
  read as it stands — with both lines off the button is the only stop; nothing is added in their
  place (owner, 2026-10-21). With both lines off the decorative picture becomes the only sign of
  the state — an accepted gap, recorded in the a11y spec.
- Target size: Compact's Button `S` (`s40`) is below the 48 the DS uses; its hit region grows to 48
  into the `s16` free above and below it — the only target in the block. Web: a transparent
  `::after` on `.status-item--compact .button`; the shipped mobile builds are unverified.
- Contrast: text ≈ 17.8:1 Day / ≈ 17.0:1 Night; the warning icon ≈ 2.7:1 in Day — a decorative
  graphic, exempt from WCAG 1.4.11 (§ 6).

---

## 11. Analytics and coverage contract

Contract per [`prototype-analytics-and-coverage.md`](https://github.com/inDriver/oymyakon-ds/blob/main/docs/prototype-analytics-and-coverage.md).

| Field | Value |
|---|---|
| `data-ds-component` | `status-item` |
| Coverage unit | yes |
| Tap target model | multiple targets — the button; the root is not a target |
| Actions | `tap` (on the button) |
| Internal targets | `action` |
| Emits value | no |

Attribute model: `data-ds-preset` = the type (`success`, `error`, `no-connection`, `empty-state`,
`warning`, `custom`), `data-ds-variant` = the size (`standard`, `compact`), `data-ds-state` =
`standard` or `skeleton`. The button is its own coverage unit (`data-ds-component="button"`).

```html
<div class="status-item status-item--error"
     data-ds-component="status-item" data-ds-component-id="{screen}.{module}.{instance}"
     data-ds-preset="error" data-ds-variant="standard" data-ds-state="standard">
  <div class="status-item__top">
    <img class="status-item__illustration" src="/illu/status/indrive-error-state-color.svg" alt="">
  </div>
  <div class="status-item__bottom">
    <div class="status-item__text" tabindex="-1">
      <h2 class="status-item__title">Something went wrong</h2>
      <p class="status-item__subtitle">Check the details and try again</p>
    </div>
    <button type="button" class="button button--l button--secondary"
            data-ds-component="button" data-ds-preset="l" data-ds-variant="secondary" data-ds-state="standard"
            data-ds-target="action" data-ds-action="tap">
      <span class="button__start-text"><span class="button__title-row">Try again</span></span>
    </button>
  </div>
</div>

<div class="status-item status-item--compact status-item--no-connection"
     data-ds-component="status-item" data-ds-component-id="{screen}.{module}.{instance}"
     data-ds-preset="no-connection" data-ds-variant="compact" data-ds-state="standard">
  <div class="status-item__top">
    <!-- The bare .squircle is the Custom geometry (the M box); .status-item--no-connection
         re-points its --squircle-fill / --squircle-content in shared.css. -->
    <span class="squircle" aria-hidden="true"
          data-ds-component="squircle" data-ds-preset="custom" data-ds-variant="icon" data-ds-state="standard"><span class="squircle__slot"><!-- internet-off, s24 --></span></span>
  </div>
  <div class="status-item__bottom">
    <div class="status-item__text" tabindex="-1">
      <h2 class="status-item__title">The connection is lost</h2>
    </div>
    <button type="button" class="button button--s button--secondary"
            data-ds-component="button" data-ds-preset="s" data-ds-variant="secondary" data-ds-state="standard"
            data-ds-target="action" data-ds-action="tap">
      <span class="button__start-text"><span class="button__title-row">Try again</span></span>
    </button>
  </div>
</div>

<div class="status-item status-item--compact status-item--skeleton" aria-hidden="true"
     data-ds-component="status-item" data-ds-component-id="{screen}.{module}.{instance}"
     data-ds-preset="success" data-ds-variant="compact" data-ds-state="skeleton">
  <!-- Squircle M skeleton · text bar (--status-item-skeleton-text-width) · button bar s120 × s40 -->
</div>
```

The skeleton's shapes are hidden. "Loading" is the text of a separate `role="status"` node without
`aria-busy`; `aria-busy="true"` goes on the region being loaded (a busy live region holds its own
announcement back). `.status-item__text` carries `tabindex="-1"` so the host can move focus to it
when the block replaces a whole screen; it stays out of the Tab order. The root is a plain `<div>` — no landmark (a11y spec,
Web). The heading level follows the host's outline (`h2` here); the class carries the style. The
class names of the web build are final once `shared.css` ships — this markup is the contract it
follows.

---

## Changelog

| Version | Date | Change |
|---|---|---|
| 3.2.2 | 2026-11-12 | a11y: the separate status node is now the DS-wide rule (Skeleton 3.1.4) — the "flagged on Skeleton" note resolved. No contract change. |
| 3.2.1 | 2026-11-02 | Status Draft → Ready (owner), after PR #23 was approved (JohnyGoode, 2026-10-30) and merged (`427bba1`). No contract change. |
| 3.2.0 | 2026-10-21 | Initial spec, from the Figma `[StatusItem] 3.2` page in the main components file (page `8384:570`, set `8441:6573`, Building blocks `8467:1389`, Update frame `11563:3500`). Type (success · error · no connection · empty state · warning) × Compact; Skeleton as a state; CustomStatusItem with an open top slot. Standard: the type's illustration at 240 × 240, Heading 2 title, Button `L`; Compact: a Squircle M (Custom-Squircle tinted per type in Pastel + the TextAndIcon status token; empty state an M-Squircle on `Surface/OnWhite` with a `Border/Default` border), Heading 4 title, Button `S`. Title and subtitle `TextAndIcon/Primary` (Update 3.1). TopSlot padding `s16` top and `s8` bottom (Compact `s0`), BottomSlot `s16` top/bottom and `s20` sides, both slots hug their content's height, BottomSlot gap `s24` / `s16`, text gap `s4`. Five illustrations captured as vectors to `illu/status/`. A11y (`status-item-a11y.md`): two stops at most — the text (title + subtitle merged, heading trait) and the button; the picture decorative; announcing by how the block appears (screen load: reading order · whole screen replaced: focus to the text · a section replaced: one polite announcement); an unavailable action omitted, not disabled. Both platforms ship Figma 3.1 (`DsStatusItem` on iOS and Android). Owner rulings (2026-10-21): the illustration grows 220 → 240 in 3.2, a component-local fixed length; the empty-state Squircle fill re-bound in Figma to `Surface/OnWhite` (was `Background/BaseGrey`); the skeleton BottomSlot frames padded `s20` like the slot. The illustration holds 240 in every SP mode (fixed, as in Figma); the 200 skeleton bar is a component-local value (`--status-item-skeleton-text-width`), not an SP token. Figma flags: CustomStatusItem pads the custom content in Compact only; the Compact skeleton is a flat stack outside the slot instances and nests `[Squircle] 3.5` (repo 3.4.3); the platforms bind the 3.1 property names `Illustration` / `Button` (3.2: `TopSlot` + BottomSlot booleans); stale Code Connect snippets remain in the file. Spec-reviewer pass (~92%) folded in: the Button and Squircle snippet attributes completed to their own attribute models. A11y-reviewer pass (~82%, no blockers) folded in: the iOS announcement API (an attributed-string attribute, not a flag), the Android pane / live-region mechanism, the retry flow (an identical result announced again — the deliberate exception to the Skeleton contract), the modal host, a dedicated web live region (`role="status"` is atomic), web focus on the text wrapper, the Button hint and the 44 × 44 padded target aligned to the Button contract, mobile reduced-motion gaps, every configuration read as it stands (owner — no at-least-one-line rule), the skeleton markup. Web build shipped: `shared.css` (`.status-item`, the fixed sizes pinned on `:root`, the skeleton from bare Skeleton shapes) + preview page `src/status-item.njk` (7 sections) + `capabilities.json` (adversarial review folded in: the skeleton axis, the nested Button / Squircle bindings, CustomStatusItem as its own Figma set) + registry row + manifest web binding (`web-nodes.json`, set `8441-6573`, presets = the types). Code review (PR #23, JohnyGoode, 2026-10-28) folded in: the 240 made a component-local length like the 200 and `s240` dropped from the SP scale (owner, 2026-10-30 — a scaling token cannot stay fixed when the SP mode is set on `<html>`); retry — focus decides, announce only when focus stays, move focus only when the button is gone, a Skeleton retry parks focus on the loading region; Android whole-screen swap — Views `ACTION_ACCESSIBILITY_FOCUS`, Compose `paneTitle` with the whole text (a recorded gap: no public focus API); the web skeleton announcement as a separate `role="status"` without `aria-busy`; Compact Button S hit region 48 (not 44), built on the web as a `::after`; `tabindex="-1"` on `.status-item__text` in the markup; both lines off recorded as an accepted gap; the warning icon exempt from 1.4.11 as decorative. |

---

# StatusItem — Accessibility

StatusItem tells the user how something ended or what state a screen is in, so the words carry the
whole message. Assistive tech meets it as **two stops**: the **text** (the title and the subtitle,
read as one heading) and the **button**, if there is one. A Custom top slot adds a third stop only
when its content carries information the text does not. The picture is decorative.

Label rule: ✅ "Something went wrong. Check the details and try again", heading — then "Try again,
button". ❌ "Image, error illustration" before the text; ❌ the title and the subtitle as two
separate swipes; ❌ a button labelled with the state ("Error") instead of the action.

**The picture is decorative.** The Standard illustration and the Compact Squircle repeat what the
title says — they are hidden from assistive tech on every platform and every type. Colour is not
the only signal: the error and the no-connection types share a red Squircle, and the text tells
them apart.

**The text is one heading.** The title and the subtitle merge into one element with the heading
trait, title first. The heading lets a screen-reader user jump straight to the state on a full
screen; merging keeps the state in one swipe. The pause between the two lines comes from the host's
copy — the title ends with its own punctuation in the localized string, or the platform inserts its
merge pause; the label does not rely on either alone.

**What is shown is what is read.** Every configuration the set allows is read as it stands: with
the subtitle off (the no-connection default) the text element is the title alone; with the title
off, the subtitle alone, still a heading on the platforms (on the web it stays a `<p>`, so no
heading remains — see Web); with both off there is no text element, and the button is the only stop;
with the button off too, the block has nothing to read — the picture stays decorative and nothing is
invented in its place (owner, 2026-10-21).

**Accepted gap — both lines off.** The picture is decorative *because the text carries the state*.
With both lines off that reasoning no longer holds: the picture is then the only thing that shows
the state to a sighted user, and a screen-reader user gets the button alone (or nothing). The set
allows the configuration and the owner ruled against inventing text, so this is recorded as a known
gap, not solved: a host that turns both lines off owns giving the state a text equivalent elsewhere
on the screen.

**Announce when it replaces content, not when it loads with the screen.** StatusItem is static in
itself; whether it is announced depends on how it appears, and the host decides, because only the
host knows whether the block fills the screen:

| How it appears | What happens |
|---|---|
| On screen load, with the rest of the screen | Nothing extra — read in reading order |
| Inside a modal host (the "Status" bottom sheet) | The sheet owns focus and its own pane announcement; StatusItem counts as screen load inside it |
| Replacing the whole screen's content after an action (a payment result, a failed request) | Focus moves to the text element, so the title and the subtitle are read at once. Moving focus *is* the announcement — nothing is announced on top of it |
| Replacing a section (Compact in a card or a sheet section) while focus stays elsewhere | The title and the subtitle are announced once, politely; focus stays where it is |
| After "Try again" — a result lands | **Focus decides, never both.** If focus is still where it was (the Button-Loading path below), the result is announced as for a replaced section, **even when its text is identical** to the previous one (a retry that fails again). If the focused element is gone, focus moves to the new text element, which reads it — no announcement on top |

The last row is a deliberate exception to the Skeleton contract, which does not re-announce arriving
content wholesale: a status is the one piece of content whose arrival is the news.

**The retry path — where focus is between the tap and the result.** Two paths are allowed; the
host picks one per screen:

1. **Button Loading (preferred).** The block stays; the button goes to its Loading state, which keeps
   focus (Button contract). When the result is a StatusItem that keeps its button, focus stays on it
   and the result is announced; when the result removes the button (a success, another screen),
   focus moves to the new text.
2. **Skeleton.** The block is replaced by its skeleton, which removes the focused button. Before it
   is removed the host parks focus on the loading region (a focusable, labelled container — web
   `tabindex="-1"`, TalkBack accessibility focus, VoiceOver `.layoutChanged` with the region), so
   focus never falls to the document or is lost. When the result lands, focus moves to the new text.

A Button-Loading retry whose button is removed by the result and a Skeleton retry both end with focus
on the text — so the result is read once. StatusItem has no
alert semantics — an error StatusItem is content, not an urgent alert.

## Android · TalkBack

| Element | Label | Value | Trait | Hint |
|---|---|---|---|---|
| StatusItem (root) | Not a node — a layout container; its children are the stops | — | — | — |
| Illustration / Squircle | — | — | Decorative — `contentDescription = null` / `Modifier.clearAndSetSemantics {}` | — |
| Text (title + subtitle) | `Modifier.semantics(mergeDescendants = true) { heading() }` on the TextContainer — reads "Something went wrong. Check the details and try again" | — | Heading | — |
| Button | Its visible label ("Try again") — the Button contract | — | Button | Double-tap to activate (the Button contract, unchanged) |

### Edge states

- **Replacing the screen's content:** focus moves to the merged text node. Views —
  `performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null)` on the text view (the merged
  container), which reads the title and the subtitle. Compose has no public API that moves TalkBack's
  accessibility focus: the host makes the new content a pane, `Modifier.semantics { paneTitle =
  "$title. $subtitle" }` on the block's container — the **whole text**, not the title alone, since
  the pane title is all TalkBack speaks on the change — and keeps the text node first in the block's
  traversal order, so the next swipe lands on it. That reads the state but does not place focus;
  recorded as the Compose gap, to verify on a device. `FocusRequester` is not the mechanism: it needs
  a focusable node, and `focusable()` on the text would add a keyboard Tab stop that acts on nothing.
- **Replacing a section:** `Modifier.semantics { liveRegion = LiveRegionMode.Polite }` on a node
  that exists before and after the swap — the host's section container — so the change of its
  content is announced; a live region on a freshly composed node may not fire. Not verified on the
  shipped builds.
- **Retry:** on the Button-Loading path with the button kept, a polite live region announces the
  result; it does not fire when the content does not change, so the host clears the region's content
  for one frame before setting the result. When the button is removed, focus goes to the text as for
  a screen replacement (Views `ACTION_ACCESSIBILITY_FOCUS`; Compose the pane-title gap above). On the
  Skeleton path the loading container takes accessibility focus before the button is removed.
- **No subtitle / no title:** the merged node carries the one line left; still a heading. Both
  off: no text node — the button is the only stop, read as it is.
- **No button:** the text is the only stop.
- **Custom type:** the host's top content follows the same rule — decorative unless it carries
  information the text does not; then it is its own labelled node before the text.
- **Skeleton:** every shape is hidden (`clearAndSetSemantics {}`); the loading region — the host's
  container — announces "Loading" once, politely, and is marked busy, per the Skeleton contract. The
  shipped DS skeleton sets no semantics of its own (a flagged gap of the Skeleton component), so the
  host hides the shapes explicitly.
- **Disabled:** not applicable to the block. When the action is unavailable, the host omits the
  button rather than showing it disabled, so the screen-reader user is not left on a dead stop.
- **Text scaling:** the Standard title (Heading 2) caps at 130%; the Compact title (Heading 4) and
  the subtitle scale to 150%, the DS maximum. Both are below WCAG 1.4.4's 200% (the DS-wide rule);
  the DS defines no mode above 150%, so how a shipped build treats a larger OS font scale is unverified. The lines wrap without truncation; the
  illustration holds 240 (fixed). At the larger modes the Standard block can exceed the viewport's
  height — the host's area scrolls, and the block is never clipped.
- **Target size:** Compact uses Button `S` (`s40`) — below the 48 the DS uses (Button contract;
  48dp is Android's guidance); the SP modes lift it to 52 at 130% and 60 at 150%. **Contract:** at
  100% the hit region grows to 48 into the free space above and below it (the BottomSlot's `s16` gap
  and padding) — the only target in the block, so nothing overlaps. Implemented on the web
  (`shared.css`); the shipped mobile builds are unverified and the Button contract itself does not
  pad `S`, so this is an obligation of the StatusItem implementation (e.g. `minimumInteractiveComponentSize`
  on Android, a larger `contentShape` on iOS).
- **Reduced motion:** the skeleton's shimmer zeroes at the token on the web; the shipped Android
  skeleton does not honour the system setting (a flagged gap of the Skeleton component), so the
  StatusItem skeleton inherits it.
- **RTL:** the block is centred and reads the same; the button follows its own RTL contract.

---

## iOS · VoiceOver

| Element | Label | Value | Trait | Hint |
|---|---|---|---|---|
| StatusItem (root) | Not an element — a container (`accessibilityElement(children: .contain)`) | — | — | — |
| Illustration / Squircle | — | — | Decorative — `accessibilityHidden(true)` (UIKit: `isAccessibilityElement = false`) | — |
| Text (title + subtitle) | `accessibilityElement(children: .combine)` on the TextContainer — "Something went wrong. Check the details and try again" | — | Header — SwiftUI `.isHeader`, UIKit `.header` | — |
| Button | Its visible label ("Try again") — the Button contract | — | Button — SwiftUI `.isButton`, UIKit `.button` | Double-tap to activate (the Button contract, unchanged) |

### Edge states

- **Replacing the screen's content:** SwiftUI — an `@AccessibilityFocusState` bound to the text,
  set when the block appears; moving focus reads it, nothing is announced on top. UIKit — `UIAccessibility.post(notification: .screenChanged, argument:
  textElement)` with the text's UIKit element, so VoiceOver moves to it and reads it.
- **Replacing a section:** an announcement of the title and the subtitle joined with a sentence
  separator (". "), queued so it does not cut off current speech. UIKit —
  `UIAccessibility.post(notification: .announcement, argument: NSAttributedString(string: text,
  attributes: [.accessibilitySpeechQueueAnnouncement: true]))`. SwiftUI / iOS 17+ —
  `AccessibilityNotification.Announcement(attributedText).post()`, the `AttributedString` carrying
  the announcement priority `.default`. Focus stays put.
- **Retry:** on the Button-Loading path with the button kept, an announcement is posted for each
  result, so an identical text is announced again (no clearing needed). When the button is removed,
  focus goes to the text as for a screen replacement — no announcement. On the Skeleton path,
  `UIAccessibility.post(notification: .layoutChanged, argument: loadingContainer)` before the button
  is removed.
- **No subtitle / no title · No button · Custom type:** as on Android.
- **Skeleton:** `accessibilityHidden(true)` on the shapes; the host's loading region announces once.
  Only the DS's `hasDsSkeleton` path hides the shapes — the unconditional `dsSkeleton(...)` does not
  (a flagged gap of the Skeleton component), so the host hides them or uses `hasDsSkeleton`.
- **Dimmed / Disabled:** not applicable to the block; the host omits an unavailable button.
- **Dynamic Type · Target size:** as on Android — the Standard title caps at 130%, the rest scales
  to 150%; the illustration holds 240; the host's area scrolls; Button `S`'s hit region grows to 48
  (a `contentShape` larger than the label box) — the same unverified obligation.
- **Reduced motion:** as on Android — the shipped iOS skeleton does not honour Reduce Motion (a
  flagged gap of the Skeleton component).
- **RTL:** as on Android.

### Shipped builds (not verified)

Both platforms ship `DsStatusItem` (Figma 3.1). The platform bridges carry no accessibility data, so
whether the shipped builds hide the picture, merge the text and set the heading trait is not
confirmed here. iOS requires `titleText` and `subtitleText` in its initializer, so a no-subtitle
StatusItem there passes an empty string — whether that adds an empty pause to the label is
unchecked.

---

## Web preview (reference)

- **Structure:** the root is a plain `<div>` — no landmark, no role. A named `<section>` would add a
  region landmark per status block; a full-screen host that wants one names its own `<main>` or
  `<section>`.
- **Picture:** the illustration is `<img alt="">`; the Squircle is `aria-hidden="true"`.
- **Text:** the title is a heading element (`h2` … `h4`, the level from the host's outline — the
  class carries the style, not the level); the subtitle a `<p>`. Headings and paragraphs do not
  merge in the browser, so they are read as two lines in sequence, the heading first. With the title
  off, the subtitle stays a `<p>` — no heading remains on the web (accepted; the platforms keep the
  heading trait on the merged node).
- **Button:** the DS `<button>` with its visible label; nothing added to its semantics. Compact's
  Button `S` (40 high) gets a 48-high hit region from a transparent `::after` that reaches 4 above
  and below it into the BottomSlot's free `s16` (`.status-item--compact .button::after`).
- **Announcing a replaced section:** a live region only announces changes inside an element that
  already exists, and `role="status"` is atomic — it re-reads its whole content, the button's label
  included. So the host keeps one dedicated, visually hidden `role="status"` element from the page's
  start and writes the title and the subtitle into it as text when the block appears; the block
  itself carries no live semantics. For a retry with an identical result, the host empties the
  element before writing the text again.
- **Replacing the whole screen:** focus moves to the text wrapper — `.status-item__text` carries
  `tabindex="-1"` in the markup (§ 11 of the main spec), and the host calls `.focus()` on it — so
  both lines are read, as on mobile; `tabindex="-1"` keeps it out of the Tab order. Nothing is
  announced on top of the focus move.
- **Retry:** Button-Loading path with the button kept — empty the status element, then write the
  result into it (an identical text is announced again). Button removed — `.focus()` on the new
  `.status-item__text`, no status write. Skeleton path — before the skeleton replaces the block,
  `.focus()` on the loading container (`tabindex="-1"`, `aria-label` naming what loads), so focus
  never falls to `<body>`.
- **Skeleton:** the shapes are `aria-hidden="true"` (the root carries it). "Loading" is the text of
  a **separate** `role="status"` node without `aria-busy` — a busy live region holds its own
  announcement back, and an `aria-label` change is not a live update; `aria-busy="true"` goes on the
  content region being loaded (the NumericStepper form). When the result arrives, drop `aria-busy`
  and take the skeleton out of the DOM. The DS-wide rule since Skeleton 3.1.4 (owner, 2026-11-12).
- **Forced colours** (Windows High Contrast) — browser defaults, no `.status-item` rule of its own:
  the text and the button follow the system colours; the
  Squircle fill is forced to Canvas and the icon to the text colour, so the icon stays visible; the
  illustration keeps its artwork colours (an image).
- **Reduced motion:** the skeleton shimmer and the button's press resolve to none at the token;
  nothing else moves.

### Colour and contrast

| Pairing | Tokens | Measured |
|---|---|---|
| Title / subtitle on a `Background/Primary` host | `--text-and-icon-primary` on `--background-primary` | ≈ 17.8:1 Day / ≈ 17.0:1 Night |
| Title / subtitle on a `Surface/OnWhite` host | `--text-and-icon-primary` on `--surface-on-white` | ≈ 15.7:1 Day / ≈ 13.2:1 Night |
| Squircle icon — success | `--text-and-icon-success` on `--pastel-green1` | ≈ 3.3:1 Day / ≈ 5.3:1 Night |
| Squircle icon — error · no connection | `--text-and-icon-error` on `--pastel-red1` | ≈ 3.4:1 Day / ≈ 4.1:1 Night |
| Squircle icon — warning | `--text-and-icon-warning` on `--pastel-orange1` | **≈ 2.7:1 Day** / ≈ 4.9:1 Night — a decorative graphic, exempt from WCAG 1.4.11 (the text carries the state; see the accepted gap when both lines are off); the DS-wide pastel fact recorded on Squircle |
| Squircle icon — empty state | `--text-and-icon-primary` on `--surface-on-white` | ≈ 15.7:1 Day / ≈ 13.2:1 Night |
| Illustrations | — | Decorative artwork — no contrast duty |
| Skeleton | `--skeleton-on-white` + `--skeleton-wave` | Decorative — no contrast duty |

The Pastel fills are measured composited over `--background-primary`; on another host the Night
values shift, because the Night Pastel tokens carry alpha.

---

## Machine contract — `specs/components/status-item/capabilities.json`

Axes, allowed values, defaults, constraints and CSS/Figma bindings. Read this instead of guessing what the component can do.

```json
{
  "$schema": "../component-capabilities.schema.json",
  "id": "status-item",
  "name": "StatusItem",
  "version": "3.2.2",
  "description": "A centred status block — the picture of the state (an illustration in Standard, a tinted Squircle M with an icon in Compact), a title, a subtitle and at most one secondary action — that tells the user how an operation ended or what state a screen or section is in. Content of a screen, a section or a sheet; it stays until the state changes (not a transient message, not a banner).",
  "files": {
    "spec": "specs/components/status-item/status-item.md",
    "a11y": "specs/components/status-item/status-item-a11y.md",
    "preview": "src/status-item.njk",
    "css": "src/shared/shared.css"
  },
  "figma": {
    "library": "🕹️ Oymyakon 3.40.1 (components)",
    "fileKey": "7vdl5YkZFDWvh9QvSmydsH",
    "componentSets": {
      "[StatusItem] 3.2": {
        "key": "d73a33823b7e5e0b117d3ef29f87ee41c6e013f0",
        "note": "Set 8441:6573 on page 8384:570 — Type (success · error · no connection · empty state · warning · skeleton) × Compact (false · true). Building blocks section 8467:1389 (TopSlot set 37168:136944, BottomSlot set 37168:137936 — Title / Subtitle / Button booleans live on the BottomSlot instance). CustomStatusItem is a separate set, 37170:139847 (Size = Standard · Compact), its key not captured; it pads the custom content in Compact only (spec § 3, flagged)."
      }
    },
    "capturedAt": "2026-10-21"
  },
  "root": {
    "class": "status-item",
    "dataDsComponent": "status-item"
  },
  "anatomy": {
    "top": {
      "class": "status-item__top",
      "optional": true,
      "notes": "TopSlot — the picture, centred. Padding var(--sp-s16) top, var(--sp-s8) bottom (Standard) / var(--sp-s0) (Compact), sides 0; the Compact skeleton zeroes it entirely (the root carries the s16 instead). Height hugs its content — no fixed or minimum height. Omitted when the TopSlot boolean is off. For type custom the host places DS content here."
    },
    "illustration": {
      "class": "status-item__illustration",
      "optional": true,
      "onlyWhen": {
        "compact": [
          false
        ],
        "skeleton": [
          false
        ],
        "type": [
          "success",
          "error",
          "no-connection",
          "empty-state",
          "warning"
        ]
      },
      "notes": "Standard picture — an <img alt=\"\"> of the type's vector from illu/status/, square at --status-item-illustration-size: 240px — a component-local length, not an SP token, so it holds 240 in every SP mode wherever the mode is set (owner). Decorative."
    },
    "bottom": {
      "class": "status-item__bottom",
      "notes": "BottomSlot — the text and the button, centred. Padding var(--sp-s16) top/bottom, var(--sp-s20) sides; gap var(--sp-s24) (Standard) / var(--sp-s16) (Compact), var(--sp-s8) in the skeleton state. Height hugs its content: lines wrap and grow it, a part switched off takes its space with it."
    },
    "text": {
      "class": "status-item__text",
      "optional": true,
      "notes": "TextContainer — title over subtitle, gap var(--sp-s4), centred, wrapping, never truncated. Carries tabindex=\"-1\" in the markup: the focus target when the block replaces a whole screen (the host calls .focus()), out of the Tab order. On the web it is omitted when both lines are off — an empty container would still take the BottomSlot gap (spec § 2)."
    },
    "title": {
      "class": "status-item__title",
      "optional": true,
      "notes": "Heading 2 (Standard) / Heading 4 (Compact), TextAndIcon/Primary. A heading element whose level follows the host's outline — the class carries the style, not the level."
    },
    "subtitle": {
      "class": "status-item__subtitle",
      "optional": true,
      "notes": "Main Body, TextAndIcon/Primary, a <p>."
    },
    "squircle": {
      "class": "squircle",
      "optional": true,
      "onlyWhen": {
        "compact": [
          true
        ],
        "type": [
          "success",
          "error",
          "no-connection",
          "empty-state",
          "warning"
        ]
      },
      "notes": "Compact picture — a NESTED Squircle, aria-hidden, with its .squircle__slot child holding the type's s24 icon from icons/ (fill=\"currentColor\"). success / error / no-connection / warning: the bare .squircle (Custom geometry, the M box; data-ds-preset=\"custom\") re-tinted by the StatusItem type modifier through the Squircle's own --squircle-fill / --squircle-content (Pastel fill + the TextAndIcon status token). empty-state: the Squircle's own Standard state, .squircle--m.squircle--border (data-ds-preset=\"m\"; Surface/OnWhite + Border/Default). Both carry data-ds-component=\"squircle\" data-ds-variant=\"icon\" data-ds-state=\"standard\". In the skeleton state: .squircle--m.squircle--skeleton (data-ds-preset=\"m\", data-ds-state=\"skeleton\")."
    },
    "button": {
      "class": "button",
      "optional": true,
      "notes": "The one action — a NESTED Button, Secondary: class \"button button--l button--secondary\" (Standard) / \"button button--s button--secondary\" (Compact), attributes data-ds-component=\"button\" data-ds-preset=\"l|s\" data-ds-variant=\"secondary\" data-ds-state=\"standard\" plus data-ds-target=\"action\" data-ds-action=\"tap\"; label inside .button__start-text > .button__title-row. Hugs its label, centred. Absent in the skeleton state (a button-shaped bar instead). In Compact its hit region grows to 48 through .status-item--compact .button::after (4 above and below, into the BottomSlot's free s16); the box and look are unchanged."
    },
    "skeletonArt": {
      "class": "status-item__skeleton-art",
      "optional": true,
      "onlyWhen": {
        "skeleton": [
          true
        ],
        "compact": [
          false
        ]
      },
      "notes": "Skeleton state, Standard — a bare .skeleton square at --status-item-illustration-size (240, fixed), radius var(--sp-s24). Compact uses the Squircle's own .squircle--m.squircle--skeleton instead."
    },
    "skeletonText": {
      "class": "status-item__skeleton-text",
      "optional": true,
      "onlyWhen": {
        "skeleton": [
          true
        ]
      },
      "notes": "Skeleton text bar — a bare .skeleton inside var(--sp-s2) vertical padding, width --status-item-skeleton-text-width (200px, component-local, owner); height var(--sp-s44), radius var(--sp-s8) (Standard) / height var(--sp-s16), radius var(--sp-s6) (Compact)."
    },
    "skeletonButton": {
      "class": "status-item__skeleton-button",
      "optional": true,
      "onlyWhen": {
        "skeleton": [
          true
        ]
      },
      "notes": "Skeleton button bar — a bare .skeleton var(--sp-s120) × var(--sp-s56) (Standard) / var(--sp-s40) (Compact), radius var(--sp-s20). Not a Button in its Skeleton state: Button's skeleton width is fixed at 72 / 64."
    }
  },
  "axes": {
    "type": {
      "title": "Type",
      "type": "enum",
      "values": [
        "success",
        "error",
        "no-connection",
        "empty-state",
        "warning",
        "custom"
      ],
      "default": "success",
      "css": {
        "mechanism": "Modifier .status-item--{value} on the root plus the picture it selects: Standard swaps the illustration file (illu/status/indrive-{success-state|error-state|no-internet|empty-state|warning-state}-color.svg); Compact re-points the nested Squircle — .status-item--success / --error / --no-connection / --warning carry the colour rules in shared.css; --empty-state carries none (its Squircle is .squircle--m.squircle--border, the Squircle's own Standard state); --custom carries none and the web preview stages no custom specimen — the host fills .status-item__top with DS content. Ignored in the skeleton state except as data-ds-preset."
      },
      "figma": {
        "kind": "variant-property",
        "property": "Type",
        "values": {
          "no-connection": "no connection",
          "empty-state": "empty state"
        },
        "notes": "custom is NOT a Type value in Figma — it is the separate CustomStatusItem set (Size = Standard · Compact). Figma also lists skeleton as a Type value; the contract models it as the skeleton axis / state."
      },
      "constraints": [
        "Type is content, not a state — an error StatusItem never turns into a success one; the host replaces the block.",
        "error and no-connection share a red Squircle; the text tells them apart — colour is never the only signal.",
        "custom: the host's top content is DS content only; decorative unless it carries information the text does not."
      ]
    },
    "compact": {
      "title": "Compact",
      "type": "boolean",
      "default": false,
      "css": {
        "modifier": ".status-item--compact"
      },
      "figma": {
        "kind": "variant-property",
        "property": "Compact",
        "values": {
          "false": "false",
          "true": "true"
        },
        "notes": "On CustomStatusItem the same axis is Size = Standard · Compact."
      },
      "notes": "false = Standard (illustration 240, Heading 2, Button L, BottomSlot gap s24, TopSlot bottom s8) — a screen or a tall sheet. true = Compact (Squircle M s48, Heading 4, Button S, gap s16, TopSlot bottom s0) — a section, a card, a short sheet."
    },
    "skeleton": {
      "title": "Skeleton",
      "type": "boolean",
      "default": false,
      "css": {
        "modifier": ".status-item--skeleton"
      },
      "figma": {
        "kind": "variant-property",
        "property": "Type",
        "values": {
          "true": "skeleton"
        },
        "notes": "Figma stages the skeleton as a Type value (Type=skeleton × Compact)."
      },
      "constraints": [
        "The root carries aria-hidden=\"true\" and data-ds-state=\"skeleton\"; data-ds-preset stays the type the block will show.",
        "No type modifier, no picture, no text, no Button — the shapes are bare Skeletons (status-item__skeleton-art / -text / -button) and, in Compact, the Squircle's own .squircle--m.squircle--skeleton.",
        "Compact skeleton geometry is a flat stack: root padding var(--sp-s16) top/bottom, no root gap, TopSlot padding 0 (block 180); Standard keeps the slot paddings (block 408)."
      ],
      "notes": "The loading shape of the block while its state is unknown. \"Loading\" is the text of a separate role=\"status\" node without aria-busy (a busy live region holds its own announcement back); aria-busy=\"true\" goes on the region being loaded. A Skeleton retry parks focus on the loading region before the focused button is removed."
    },
    "topSlot": {
      "title": "TopSlot",
      "type": "boolean",
      "default": true,
      "css": {
        "mechanism": "Presence of .status-item__top — the element is in the markup or it is not; no modifier class."
      },
      "figma": {
        "kind": "variant-property",
        "property": "TopSlot",
        "notes": "3.2 boolean on the root; the platforms still bind the 3.1 name Illustration."
      }
    },
    "title": {
      "title": "Title",
      "type": "boolean",
      "default": true,
      "css": {
        "mechanism": "Presence of .status-item__title."
      },
      "figma": {
        "kind": "variant-property",
        "property": "Title",
        "notes": "BottomSlot boolean. A boolean of the nested BottomSlot instance, not of the StatusItem set."
      }
    },
    "subtitle": {
      "title": "Subtitle",
      "type": "boolean",
      "default": true,
      "css": {
        "mechanism": "Presence of .status-item__subtitle."
      },
      "figma": {
        "kind": "variant-property",
        "property": "Subtitle",
        "notes": "BottomSlot boolean; the no-connection type stages it off. A boolean of the nested BottomSlot instance, not of the StatusItem set."
      }
    },
    "button": {
      "title": "Button",
      "type": "boolean",
      "default": true,
      "css": {
        "mechanism": "Presence of the nested Button. An unavailable action is omitted, never shown disabled."
      },
      "figma": {
        "kind": "variant-property",
        "property": "Button",
        "notes": "BottomSlot boolean. A boolean of the nested BottomSlot instance, not of the StatusItem set."
      }
    },
    "titleText": {
      "title": "Title text",
      "type": "text",
      "default": "Title",
      "customizable": "What happened, in one short line (\"Payment completed\"). Wraps, never truncated. Read as is — with the subtitle it forms the one heading stop."
    },
    "subtitleText": {
      "title": "Subtitle text",
      "type": "text",
      "default": "Subtitle",
      "customizable": "Why, or what to do next. Wraps, never truncated."
    },
    "buttonLabel": {
      "title": "Button label",
      "type": "text",
      "default": "Try again",
      "customizable": "The action as a verb (\"Try again\", \"Done\") — never the state (\"Error\"). It is the button's accessible name."
    }
  },
  "states": {
    "default": [
      "standard",
      "skeleton"
    ]
  },
  "constraints": [
    "Width fills the host; no width of its own (Figma stages 360). Height hugs: TopSlot and BottomSlot have no fixed or minimum height — the quoted 428 / 196 (skeleton 408 / 180) are the one-line copy at 100 %, never sizes to set.",
    "No surface: no fill, border or radius — the block sits on the host's background. No padding or gap on the root in the standard state; the slots own the spacing (the Compact skeleton is the one exception: root padding var(--sp-s16) top/bottom).",
    "At most ONE action. A second action belongs to the host (a sheet footer, a screen's bottom bar).",
    "The illustration is fixed at 240 (--status-item-illustration-size: 240px, a component-local length — not an SP token) in every SP mode, wherever the mode is set; the text and the spacing scale around it.",
    "Not interactive itself: no pressed, hover, focused or disabled state of the block; the nested Button carries its own states.",
    "Skeleton is a state, not a type: bare Skeleton shapes sized by the block (the Compact square is the Squircle's own skeleton), hidden from assistive tech. \"Loading\" is announced by a separate role=\"status\" node without aria-busy; aria-busy goes on the region being loaded.",
    "The picture is decorative on every type — <img alt=\"\"> / Squircle aria-hidden; the title + subtitle are the single heading stop, the button the second.",
    "No live semantics on the block. Replacing a whole screen moves focus to .status-item__text (tabindex=\"-1\") — that read is the announcement. Replacing a section announces through a dedicated, pre-existing hidden role=\"status\" owned by the host. After a retry, focus decides: announce only when focus stayed (Button Loading), move focus only when the focused button is gone — never both.",
    "No alert semantics — an error StatusItem is content, not an urgent alert.",
    "Not inside a list row or beside other content in a row; not a transient confirmation (Notification / Snackbar) nor a page banner (HeaderAlert).",
    "Touch target — Compact's Button S (var(--sp-s40)) is below the DS 48; its hit region grows to 48 into the var(--sp-s16) free above and below it (web: .status-item--compact .button::after; the shipped mobile builds are unverified).",
    "With both text lines off the decorative picture is the only sign of the state — an accepted gap (owner: no invented text); a host that turns both off gives the state a text equivalent elsewhere."
  ],
  "analytics": {
    "dataDsComponent": "status-item",
    "action": "tap",
    "targets": [
      "action"
    ],
    "notes": "Multiple-target model: the root is the coverage unit, not a target (data-ds-component=\"status-item\", data-ds-component-id=\"{screen}.{module}.{instance}\"); the nested Button is the only tap target (data-ds-target=\"action\", data-ds-action=\"tap\") and its own coverage unit. data-ds-preset = the type, data-ds-variant = standard | compact, data-ds-state = standard | skeleton. The nested Squircle (data-ds-component=\"squircle\") and the skeleton shapes (data-ds-component=\"skeleton\" data-ds-preset=\"custom\") are their own coverage units. Emits no value."
  },
  "rtl": {
    "supported": true,
    "notes": "Everything is centred, so the block reads the same in both directions; the Figma set stages no RTL axis. The Button follows its own RTL contract; no illustration or icon is directional, nothing mirrors."
  }
}
```
