> ## 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/plate-number.html
> Source: specs/components/plate-number/plate-number.md, specs/components/plate-number/plate-number-a11y.md, specs/components/plate-number/capabilities.json

---

# PlateNumber · Oymyakon DS 3

> The car's plate number during the ride — an outlined box around the registration, like the plate
> itself, so the passenger can match it to the car that pulls up.

**Version:** 3.0.1 · **Status:** Ready · **Figma:** `[PlateNumber] 3.0` (main components file `7vdl5YkZFDWvh9QvSmydsH`, page `36922:1897`, set `36922:11969`, key `6845f1d8…`; Specification frame `36922:11421`)

---

## 1. Description

PlateNumber shows the plate number of the car assigned to the ride. It sits under the car's name —
"Silver Mercedes-Benz C-Class" — in the ride details and in the activity widget, so the passenger
can check the plate against the car that arrives. The outlined box reads as a plate at a glance; the
text inside is the registration as issued, with the region where the country's plates carry one
("ขส1993 พระนครศรีอยุธยา").

Two sizes: **M** for larger blocks (the ride details), **S** for smaller ones (the activity widget, a
small card detail). A long value wraps to the next line inside the box — it is never truncated.
While the ride data loads, a fixed-size **Skeleton** takes the box's place.

### Platform implementations

| Platform | Entry point | Version | Notes |
|---|---|---|---|
| iOS | — | — | Not in the platform bridge (manifest seed, 2026-10-20) |
| Android | — | — | Not in the platform bridge (manifest seed, 2026-10-20) |
| Flutter | — | — | Not shipped |
| Web | `src/shared/shared.css` (`.plate-number`) | 3.0.0 | Reference implementation, this repo; preview page `src/plate-number.njk` — the first build of this component on any platform |

---

## 2. Anatomy

```
PlateNumber (.plate-number)          hug × hug · s4 left/right, no vertical padding · inside border
└── Text (.plate-number__text)       the plate number · wraps, never truncated · TextAndIcon/Primary

PlateNumber — Skeleton (.plate-number--skeleton)
└── Skeleton shape                   a fixed box in Skeleton/OnWhite with the DS shimmer · no text
```

| Part | Required | Description |
|---|---|---|
| **Container** | Required | A horizontal auto-layout that hugs its text: padding `s4` left/right, `s0` top/bottom; a minimum height (`s28` M, `s16` S); `Background/Primary` fill, a `TextAndIcon/Primary` inside border (`s2` M, 1dp S) and a corner radius (`s8` M, `s4` S) |
| **Text** | Required | The plate number in `TextAndIcon/Primary` — `Body/Promo Body` (M) or `Body/Compact Body` (S); one line by default, wraps to the next line when the host is narrower than the value |
| **Skeleton shape** | Skeleton only | Replaces the text and the border: a fixed box (`s140` × `s28` M, `s96` × `s16` S) in `Skeleton/OnWhite` with the DS shimmer, the size's corner radius |

- The box has no width of its own: it hugs the text, and the host's width is the only cap.
- The Figma container also carries a gap of `s10` — inert, the container holds one child.

### RTL layout

The Figma set stages `RTL = True`: the same box with the text aligned to the right edge. A one-line
value looks the same either way; a wrapped value aligns its lines to the right. The box is
symmetric and does not mirror; the skeleton's shimmer runs in reverse.

The plate keeps its own character order whatever the layout's direction: the value is its own
bidi paragraph, so a Latin or Thai plate in an RTL layout still reads left to right inside the box
("ขส1993 พระนครศรีอยุธยา" is not reordered into "พระนครศรีอยุธยา ขส1993"). On the web: `dir="rtl"`
on the root or an ancestor right-aligns the lines; the text is `unicode-bidi: plaintext`.

---

## 3. Variants and sizes

| Axis | Values | Default | Web mechanism |
|---|---|---|---|
| **Size** | `m` · `s` | `m` | The bare `.plate-number` is M; `.plate-number--s` re-points the size's text style, minimum height, border and radius |
| **Skeleton** | `false` · `true` | `false` | `.plate-number--skeleton` — the fixed shape, no text element |
| **RTL** | `false` · `true` | `false` | `dir="rtl"` on the root or an ancestor — the lines align right, the shimmer reverses; the value keeps its own character order |

| Size | Text | Min height | Padding L/R | Border | Radius | Skeleton |
|---|---|---|---|---|---|---|
| **M** (default) | Promo Body 20/28 | `s28` | `s4` | `s2` | `s8` | `s140` × `s28` |
| **S** | Compact Body 14/16 | `s16` | `s4` | 1dp | `s4` | `s96` × `s16` |

- Use **M** for larger blocks, such as the ride details; **S** for smaller ones, such as the activity
  widget or a small card detail.
- The Figma set stages six of the eight combinations — `Size = M, Skeleton = True` has no RTL
  variant and `Size = S, Skeleton = True` no LTR one. The skeleton shape is the same in both
  directions; flagged for the Figma side.

---

## 4. States

| State | What shows |
|---|---|
| **Standard** | The box in `Background/Primary` with the `TextAndIcon/Primary` border and the plate number in `TextAndIcon/Primary` |
| **Skeleton** | A fixed-size `Skeleton/OnWhite` box with the DS shimmer, the size's corner radius; no border, no text |

- PlateNumber is not interactive: no Pressed, Hover, Focused or Disabled state. A Pressed or Disabled
  look is not added by analogy — the Figma set stages none.
- The skeleton layout is static: its size does not adapt to the value that will replace it.

---

## 5. Animation and behavior

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

| Event | Pattern | Token | Notes |
|---|---|---|---|
| Skeleton shimmer | `Patterns/Shimmer` | `var(--pattern-shimmer)` | The DS skeleton sweep, the same as the Skeleton component; reversed in RTL (`animation-direction: reverse`) |
| Skeleton → Standard | — | — | The host swaps the skeleton for the content; PlateNumber declares no transition of its own |
| Text change | — | — | The box re-hugs the new value in the same frame — no width tween |

- No tap, no focus, no motion in the Standard state.
- Reduced motion: `--pattern-shimmer` resolves to `none` at the token, so the skeleton holds still;
  nothing else moves.

---

## 6. Color tokens

| Element | Token | Notes |
|---|---|---|
| Container fill | `var(--background-primary)` | White in Day, dark grey in Night |
| Border | `var(--text-and-icon-primary)` | Inside stroke — the plate's outline; bound to the text token in Figma, so the outline and the text are one colour |
| Text | `var(--text-and-icon-primary)` | |
| Skeleton fill | `var(--skeleton-on-white)` | |
| Skeleton wave | `var(--skeleton-wave)` | The shimmer's highlight |

- Contrast: `TextAndIcon/Primary` on `Background/Primary` ≈ 17.8:1 Day / ≈ 17.0:1 Night — the text
  and the border alike.
- On a `Background/Primary` host the fill merges with the page and the border alone draws the box —
  the plate still reads.

---

## 7. Typography

| Element | Size | Style | Tokens | Scaling |
|---|---|---|---|---|
| Text | M | Body/Promo Body — 20/28, Suisse Intl Book | `var(--text-body-promo-body-*)` | `cap130` — grows to 130% and stays there at 150% (Figma binds `SP/130% ratio`) |
| Text | S | Body/Compact Body — 14/16, Suisse Intl Book | `var(--text-body-compact-body-*)` | `full` — grows to 150% (Figma binds `SP/150% ratio`) |

The text keeps its own characters and case — the registration as issued, no letter-spacing or
case transform.

- Figma binds Promo Body for M although the typography rules reserve it for promo screens — the
  Squircle M precedent; flagged for the owner.

---

## 8. Spacing

Figma binds the paddings, radius and border to `SP/100% ratio`: on the platforms they keep their size
while the text scales, and the box grows with the text. 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); S's 1dp border is the one value that never scales, as on Tag.

| Property | Token | M @100% | S @100% | Notes |
|---|---|---|---|---|
| Width | — | hugs the text | hugs the text | No maximum of its own — the host's width caps it, and a longer value wraps (owner, 2026-10-20; the Figma S variants cap at 230, off the SP scale) |
| Min height | `var(--sp-s28)` / `var(--sp-s16)` | 28 | 16 | The text's line height; a wrapped value adds a line |
| Padding — left / right | `var(--sp-s4)` | 4 | 4 | |
| Padding — top / bottom | `var(--sp-s0)` | 0 | 0 | No vertical padding — the text's line height sets the height |
| Border | `var(--sp-s2)` / 1dp | 2 | 1 | Inside — adds nothing to the size. S's 1dp has no SP token — the Tag / Squircle border precedent. Web: an outline pulled inside by its own width, so it survives forced colours |
| Corner radius | `var(--sp-s8)` / `var(--sp-s4)` | 8 | 4 | |
| Skeleton width | `var(--sp-s140)` / `var(--sp-s96)` | 140 | 96 | Fixed — S is 98 in Figma, off the scale; `s96` (owner, 2026-10-20) |
| Skeleton height | `var(--sp-s28)` / `var(--sp-s16)` | 28 | 16 | The size's min height |
| Skeleton padding | `var(--sp-s0)` | 0 | 0 | |

---

## 9. Usage context

### When to use PlateNumber

- The ride details on the ride screen — M, under the car's name ("Silver Mercedes-Benz C-Class"),
  next to the car picture: "Driver is waiting for you".
- The activity widget and other small cards — S, under the car's name: "Driver is waiting for you —
  2:59".
- Any plate number the passenger checks against a real car — short ("1993") or long, with a region
  ("ขส1993 พระนครศรีอยุธยา", wrapping in a narrow card).

### When not to use

- For a code the user types or copies — a promo code ("QUICK50"), an order number. PlateNumber is a
  plate, not a code field; a label is a Tag.
- As a button or a link — it is not interactive.
- With the value shortened: a plate is shown whole — a long one wraps, never ends in "…".
- For the car's name, its colour or the driver's details — those are the text around it.

### Related components

- [Tag](https://super-dollop-pzmo65r.pages.github.io/tag.md) — a status or a category label.
- [Skeleton](https://super-dollop-pzmo65r.pages.github.io/skeleton.md) — the loading shape and its shimmer.
- [Cell](https://super-dollop-pzmo65r.pages.github.io/cell.md) — in the Figma ride screen the plate sits in a Cell's StartText, under the
  car's name (the preview page's Usage hosts are stand-ins).

---

## 10. Accessibility

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

- **Single focus**: the plate number is one element — "Transport plate number: P H X 5 6 8 C Y". The
  prefix is a localized string; the registration is read character by character.
- A region name is read as a word, after the registration: "Transport plate number: ข ส 1 9 9 3,
  พระนครศรีอยุธยา" (owner, 2026-10-20).
- No role of its own — static text; not focusable for the keyboard, not a button.
- The box and its border are decorative.
- Skeleton: not announced and not focusable — the host's loading region announces once.
- Inside a host that merges its children into one label (a Cell row read as one, a button), the
  plate's phrase — the registration still spelled — becomes part of the host's label.
- The plate's run carries its own language (a Thai plate is tagged Thai), so the reader switches
  voice for it.
- Text scaling: M (Promo Body) caps at 130%, S (Compact Body) scales to 150%, the DS maximum; the
  value wraps, never truncates. M's cap is the DS-wide Promo Body rule, below WCAG 1.4.4's 200% —
  flagged for the owner.
- Contrast: text and border ≈ 17.8:1 Day / ≈ 17.0:1 Night on the fill.

---

## 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` | `plate-number` |
| Coverage unit | yes |
| Tap target model | none — non-interactive |
| Actions | none |
| Internal targets | none |
| Emits value | no |

Attribute model: `data-ds-preset` = the size (`m`, `s`), `data-ds-state` = `standard` or `skeleton`.
RTL is the reading direction, not a preset.

```html
<span class="plate-number"
      data-ds-component="plate-number" data-ds-component-id="ride.plate"
      data-ds-preset="m" data-ds-state="standard">
  <span class="visually-hidden">Transport plate number: P H X 5 6 8 C Y</span>
  <span class="plate-number__text" aria-hidden="true">PHX568CY</span>
</span>

<span class="plate-number"
      data-ds-component="plate-number" data-ds-component-id="ride.plate"
      data-ds-preset="m" data-ds-state="standard">
  <span class="visually-hidden">Transport plate number: <span lang="th">ข ส 1 9 9 3, พระนครศรีอยุธยา</span></span>
  <span class="plate-number__text" aria-hidden="true" lang="th">ขส1993 พระนครศรีอยุธยา</span>
</span>

<span class="plate-number plate-number--s plate-number--skeleton" aria-hidden="true"
      data-ds-component="plate-number" data-ds-component-id="widget.plate"
      data-ds-preset="s" data-ds-state="skeleton"></span>
```

The spoken string is the visually hidden span; the visible text is hidden from assistive tech, so
the plate is read once, the registration character by character. A plate in another script tags its
run with `lang`, in the spoken span as well as the visible one.

---

## Changelog

| Version | Date | Change |
|---|---|---|
| 3.0.1 | 2026-11-12 | Status Draft → Ready (owner, 2026-11-12) — the backlog sweep: merged on main, web build shipped, no owner decision open. No contract change; Figma-side flags recorded above stay open on the Figma side. |
| 3.0.0 | 2026-10-20 | Initial spec, from the Figma `[PlateNumber] 3.0` set in the main components file (page `36922:1897`, set `36922:11969`) and its Specification frame (`36922:11421`). Two sizes — M (Promo Body 20/28, min height `s28`, `s2` border, radius `s8`) and S (Compact Body 14/16, min height `s16`, 1dp border, radius `s4`); a hug box with `s4` side padding and no vertical padding, `Background/Primary` fill, `TextAndIcon/Primary` text and inside border; the value wraps, never truncates. States Standard and Skeleton (a fixed `s140` × `s28` / `s96` × `s16` box in `Skeleton/OnWhite` with `--pattern-shimmer`). RTL right-aligns the lines; the plate keeps its own character order. Web: the border is an outline pulled inside by its own width (survives forced colours), the text `unicode-bidi: plaintext`; on the web the SP-bound paddings, radius and border follow the SP mode (Figma keeps them at 100%). A11y: single focus, "Transport plate number" + the registration character by character (Android: `clearAndSetSemantics` with a `VerbatimTtsAnnotation` in the `text`; iOS: `.accessibilitySpeechSpellOut` on the registration run only), a region name as a word, the plate's run tagged with its language; inside a merging host the phrase joins the host's label; M caps at 130% (the DS-wide Promo Body rule, below WCAG 1.4.4 — flagged), S scales to 150%. Spec-reviewer and a11y-reviewer pass folded in before release. Owner rulings (2026-10-20): its own sidebar page; S skeleton `s96` (Figma 98, off the scale); no maximum width on the web — the host caps it (Figma S 230, off the scale); a region name read as a word. Figma flags: no component description; S's 1dp border unbound (no `s1` token); two Skeleton × RTL combinations not staged; the frame calls the first state "Default" (DS: Standard). Neither mobile platform ships it. |

---

# PlateNumber — Accessibility

PlateNumber is static text with one job: the passenger checks the plate against the car that pulls
up. It is **one element — single focus** — announced as a localized prefix and the registration read
**character by character**, so "PHX568CY" is never read as a word or a number.

Label rule: ✅ "Transport plate number: P H X 5 6 8 C Y", ❌ "phx five hundred sixty-eight cy", ❌
"PHX568CY" with no prefix. The prefix ("Transport plate number", from the Figma specification) says
what the characters are; it is a localized string, not the visible text.

**A region name is read as a word** (owner, 2026-10-20). Where the country's plates carry a region —
a Thai province — the registration is spelled and the region follows as a word: "Transport plate
number: ข ส 1 9 9 3, พระนครศรีอยุธยา". Spelling a province name letter by letter makes it
unrecognisable. The spell-out covers the registration run only — never the prefix or the region.

**The plate's language is tagged.** The spoken phrase mixes the interface language (the prefix) with
the plate's script (a Thai registration and province). The plate's run carries its own language, so
the reader switches to a voice that can say it.

The box, its fill and its border are decorative — never announced. PlateNumber has no role of its own
(Figma: "Role: -") and no action.

## Android · TalkBack

| Element | Label | Value | Trait | Hint |
|---|---|---|---|---|
| PlateNumber (root) | `clearAndSetSemantics { text = … }` — an `AnnotatedString`: "Transport plate number: " + the registration under `pushTtsAnnotation(VerbatimTtsAnnotation(registration))` + ", " + the region name, if any; the plate's run in a `SpanStyle(localeList = LocaleList("th"))` (a `LocaleSpan` to TalkBack). Views: a `SpannableString` with `TtsSpan.VerbatimBuilder` and a `LocaleSpan` | — | None — static text, one node | — |
| Text | Not a node — cleared by the root's `clearAndSetSemantics` | — | — | — |
| Box / border | — | — | Decorative — drawn, not a node | — |

- **The trap:** the bare `Text("PHX568CY")` is read as a word ("phx five hundred sixty-eight cy")
  with no prefix. Two half-fixes keep it: `Modifier.semantics(mergeDescendants = true)` still merges
  the visible text into the node, so the plate is read as a word (or twice); and
  `contentDescription` is a plain string that cannot carry the verbatim span. The root's
  `clearAndSetSemantics` with the `text` property is the one mechanism.

### Edge states

- **Inside a host:** the single focus holds where the plate is its own element. In a host that
  merges its children into one node (a Cell row read as one) the plate's phrase — the registration
  still spelled — becomes part of the host's one label, not a separate swipe.
- **Skeleton:** not announced — the skeleton box is `importantForAccessibility="no"` (Compose:
  `clearAndSetSemantics {}`); the loading container announces once, politely ("Loading"), 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 box explicitly. When the plate lands it is read in its
  place, not announced on its own.
- **Disabled / Error:** not applicable — no such states.
- **Text scaling:** M (Promo Body) caps at 130%; S (Compact Body) scales to 150%, the DS maximum.
  The value wraps to the next line, never truncates, so the whole plate stays visible. M's 130% cap is
  the DS-wide Promo Body rule — below WCAG 1.4.4's 200%; flagged for the owner.
- **RTL:** the lines align right; the plate keeps its own character order, so the spoken order
  matches the visible one; the announcement is unchanged.
- **Live update:** a reassigned driver changes the plate; PlateNumber is not a live region — the host
  announces the change of car, if anything.

---

## iOS · VoiceOver

| Element | Label | Value | Trait | Hint |
|---|---|---|---|---|
| PlateNumber (root) | `accessibilityAttributedLabel`: "Transport plate number: " + the registration run with `.accessibilitySpeechSpellOut` + ", " + the region name, if any; the plate's runs with `.accessibilitySpeechLanguage` ("th"). SwiftUI: an `AttributedString` with the same attributes on the registration run only — `.speechSpellsOutCharacters()` on the whole view would spell the prefix and the region too | — | `.staticText` (SwiftUI `.isStaticText`) | — |
| Text | Not an element of its own — `accessibilityElement(children: .ignore)` on the root | — | — | — |
| Box / border | — | — | Decorative — not an element | — |

### Edge states

- **Inside a host:** as on Android — in a host that combines its children (`children: .combine`) the
  plate's phrase joins the host's one label, the registration still spelled.
- **Skeleton:** `accessibilityHidden(true)` on the skeleton box; the loading container announces
  once. Only the DS's `hasDsSkeleton` path hides the shapes — the unconditional `dsSkeleton(...)`
  does not (a flagged gap of the Skeleton component), so hide the box explicitly or use
  `hasDsSkeleton`.
- **Disabled / Dimmed / Error:** not applicable.
- **Dynamic Type:** M caps at 130%, S scales to 150%, the DS maximum; the value wraps, never
  truncates.
- **RTL / Live update:** as on Android.

---

## Web preview (reference)

- **The spoken string is a `.visually-hidden` span** — "Transport plate number: P H X 5 6 8 C Y" —
  and the visible `.plate-number__text` is `aria-hidden="true"`: the plate is read once, in one
  phrase, the registration spaced so each character is its own token. A Thai plate's run inside the
  spoken span carries `lang="th"`; the visible text keeps its own `lang` for glyphs and line breaks.
  No `aria-label` on the root `<span>` — naming a generic element is prohibited (ARIA 1.2).
- **No role, no `tabindex`** — static text in the reading order; the keyboard never stops on it.
- **Inside a host:** a host whose own `aria-label` replaces its content (a button, a link) carries the
  plate's phrase in that label.
- **Skeleton:** `aria-hidden="true"` on the empty box, `pointer-events: none` and `user-select: none`;
  the loading container carries `role="status"` + `aria-busy="true"` and the one polite announcement.
  When the plate arrives, drop `aria-busy` and take the skeleton out of the DOM (Skeleton a11y
  contract).
- **Forced colours** (Windows High Contrast): the border is an outline, so it takes the system text
  colour and the fill the Canvas colour — the plate outline remains. The skeleton has no outline and
  its fill is forced to Canvas, so it disappears; the container's "Loading" status carries the state.
- **Reduced motion:** the skeleton shimmer zeroes at the token (`--pattern-shimmer: none`); nothing
  else moves.

### Colour and contrast

| Pairing | Tokens | Measured |
|---|---|---|
| Text on the fill | `--text-and-icon-primary` on `--background-primary` | ≈ 17.8:1 Day / ≈ 17.0:1 Night |
| Border on the fill | `--text-and-icon-primary` on `--background-primary` | ≈ 17.8:1 Day / ≈ 17.0:1 Night — decorative, no non-text contrast duty; on a `--background-primary` host it alone draws the box |
| Skeleton | `--skeleton-on-white` + `--skeleton-wave` | Decorative — no contrast duty |

---

## Machine contract — `specs/components/plate-number/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": "plate-number",
  "name": "PlateNumber",
  "version": "3.0.1",
  "description": "The car's plate number during the ride — an outlined hug box (s4 side padding, no vertical padding) around the registration, M (Promo Body) for the ride details or S (Compact Body) for the activity widget; wraps, never truncates; a fixed-size skeleton while loading. Static text: one focus, read character by character.",
  "files": {
    "spec": "specs/components/plate-number/plate-number.md",
    "a11y": "specs/components/plate-number/plate-number-a11y.md",
    "preview": "src/plate-number.njk",
    "css": "src/shared/shared.css"
  },
  "figma": {
    "library": "🕹️ Oymyakon (components) — main file",
    "fileKey": "7vdl5YkZFDWvh9QvSmydsH",
    "componentSets": {
      "[PlateNumber] 3.0": {
        "key": "6845f1d8dd01b2b047b0f5c928cf1ba403f5eadc",
        "note": "Set 36922:11969 on page 36922:1897 — RTL × Size × Skeleton, six of the eight combinations staged (M Skeleton has no RTL variant, S Skeleton no LTR one); Specification frame 36922:11421."
      }
    },
    "capturedAt": "2026-10-20"
  },
  "root": {
    "class": "plate-number",
    "dataDsComponent": "plate-number"
  },
  "anatomy": {
    "text": {
      "class": "plate-number__text",
      "onlyWhen": { "skeleton": [false] },
      "notes": "The visible plate number in --text-and-icon-primary, the size's text style; wraps to the next line, never truncated. aria-hidden on the web — the spoken string is a .visually-hidden sibling (\"Transport plate number: P H X 5 6 8 C Y\")."
    }
  },
  "axes": {
    "size": {
      "title": "Size",
      "type": "enum",
      "values": ["m", "s"],
      "default": "m",
      "css": {
        "modifier": ".plate-number--s",
        "mechanism": "m is the bare .plate-number; .plate-number--s re-points the text style (Compact Body), the min height (s16), the border (1dp) and the radius (s4)"
      },
      "figma": { "kind": "variant-property", "property": "Size", "values": { "m": "M", "s": "S" } },
      "constraints": [
        "M: Promo Body 20/28 (cap130), min height s28, s2 inside border, radius s8 — larger blocks such as the ride details.",
        "S: Compact Body 14/16 (full scaling), min height s16, 1dp inside border, radius s4 — smaller blocks such as the activity widget."
      ]
    },
    "skeleton": {
      "title": "Skeleton",
      "type": "boolean",
      "default": false,
      "css": { "modifier": ".plate-number--skeleton" },
      "figma": { "kind": "variant-property", "property": "Skeleton", "values": { "false": "False", "true": "True" } },
      "constraints": [
        "A fixed box — s140 × s28 (M), s96 × s16 (S; Figma 98, owner 2026-10-20) — in --skeleton-on-white with --pattern-shimmer; no border, no text; the size does not adapt to the coming value.",
        "aria-hidden on the web; the loading container announces once."
      ]
    },
    "rtl": {
      "title": "RTL",
      "type": "boolean",
      "default": false,
      "css": { "mechanism": "dir=\"rtl\" on the root or an ancestor: the lines align right (.plate-number:dir(rtl)); the skeleton shimmer reverses. The text is unicode-bidi: plaintext, so the value keeps its own character order in either direction" },
      "figma": { "kind": "variant-property", "property": "RTL", "values": { "false": "False", "true": "True" } }
    }
  },
  "states": {
    "default": ["standard", "skeleton"]
  },
  "analytics": {
    "dataDsComponent": "plate-number",
    "action": "none",
    "notes": "Coverage unit. No tap target. data-ds-preset = the size (m, s), data-ds-state = standard | skeleton; RTL is the reading direction, not a preset."
  },
  "rtl": {
    "supported": true,
    "notes": "The lines align right in RTL; the box does not mirror (it is symmetric); the skeleton shimmer runs in reverse. The plate keeps its own character order — a Latin or Thai plate in an RTL layout is not reordered."
  },
  "constraints": [
    "Not interactive: no pressed, hover, focused or disabled state, no role, no tabindex.",
    "Hug width with no maximum of its own — the host caps it and a long value wraps (owner, 2026-10-20; the Figma S variants cap at 230, off the SP scale); never truncated, never shortened.",
    "Padding s4 left/right, s0 top/bottom. Figma binds the paddings, radius and border to SP/100% ratio (they keep their size while the text scales); on the web the --sp-* tokens follow the SP mode — a repo-wide deviation; S's 1dp border never scales.",
    "Colours are fixed: --background-primary fill, --text-and-icon-primary text and border; no re-pointing.",
    "Single focus: \"Transport plate number\" (localized) + the registration read character by character; a region name read as a word after it (owner, 2026-10-20); the plate's run tagged with its language. The box and border are never announced. Inside a host that merges its children, the phrase joins the host's label.",
    "Neither mobile platform ships it — no manifest node binding until a platform bridge carries [PlateNumber] 3.0."
  ]
}
```
