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

---

# Tag · Oymyakon DS 3

> Compact static label for categorization, status display, and metadata annotation.

**Version:** 3.1.0 · **Status:** Draft · **Figma:** `[Tag] 3.1` (node `4081:5179`, page `4053:59`)

---

## 1. Description

Tag is a small inline label that marks content with a category, status, or attribute. It carries
a text label and up to two optional icon slots. Tag is non-interactive: it renders no press state
and owns no action. It never stretches to fill a container — width hugs content, and height hugs
the content stack (`s16` content + 2 × `s4` padding = 24 at 100%).

The Figma line ships as a base + preset pair behind the `[Tag] 3.1` wrapper:

| Preset | Figma set | Contract |
|---|---|---|
| **Standard** (`StandardTag`) | `Style` × `RTL` variants | Colors are fixed per Style value; nothing is overridable |
| **Custom** (`CustomTag`) | `State` variants | Every color re-points to any DS token; the label's text style is swappable |

On the web the pair collapses into one root class: `.tag` is the Standard Surface form, Style
values are modifier classes, and the Custom preset is the same markup with the `--tag-fill` /
`--tag-content` / `--tag-border` custom properties re-pointed on the instance.

---

## 2. Anatomy

```
Tag (.tag)                        horizontal autolayout · padding s4 · gap s0 · radius s10
├── StartSlot (.tag__slot--start)   IconContainer s16 — optional
├── Label (.tag__label)             CompactBody · own s2 side padding — required
├── EndSlot (.tag__slot--end)       IconContainer s16 — optional
└── Border (.tag--border)           inset 1dp stroke layer — off by default
```

| Part | Type | Default | Description |
|---|---|---|---|
| **StartSlot** | IconContainer `s16` | Hidden on the web | Leading icon. In Figma a boolean prop (`🔴StartSlot`), shown in the master |
| **Label** | text node | Required | CompactBody. Carries its own `s2` side padding (Figma's `TextContainer`), so text sits `s6` from a bare edge and `s2` from an icon with no per-side padding switches |
| **EndSlot** | IconContainer `s16` | Hidden on the web | Trailing icon. In Figma a boolean prop (`🔵EndSlot`) |
| **Border** | inset stroke | **Off** | A separate 1dp inside stroke (`.tag--border`). It never affects layout: the box is the same size with and without it |

The root inherits its content color: label and icons take `currentColor` from `--tag-content`.

### RTL layout

RTL defaults to **Off**; the Figma set carries explicit `RTL=On` variants for every Style.

| Element | RTL behaviour |
|---|---|
| Slot order | Reverses: StartSlot renders on the right, EndSlot on the left. On the web `dir="rtl"` flips the flex row — the DOM order does not change |
| Padding | Symmetric (`s4` uniform, label `s2` both sides) — nothing to mirror |
| Non-directional icons | Do not flip (the master's `hint-outline` / `info-outline` defaults stay upright) |
| Directional icons (arrow, chevron, back) | Mirror horizontally, per the icon's own RTL variant |

---

## 3. Variants and sizes

Tag has one size. All variability sits on four axes:

| Axis | Values | Default | Web mechanism |
|---|---|---|---|
| **Preset** | `standard` · `custom` | `standard` | Standard = modifier classes; Custom = `--tag-*` custom properties on the instance |
| **Style** (Standard) | `surface` · `brand` · `color` · `inverse` · `disabled` · `skeleton` | `surface` | `.tag` is Surface; others are `.tag--{style}` |
| **State** (Custom) | `default` · `skeleton` | `default` | `.tag--skeleton` is shared by both presets |
| **StartSlot / EndSlot** | shown · hidden | hidden (web) | Presence of the `.tag__slot--start` / `.tag__slot--end` element |
| **Border** | off · on | **off** | `.tag--border` |

In Figma the Custom preset additionally allows swapping the label's text style: `CompactBody`
(default), with `Caption`, `PromoBody`, and `Heading1` as the preferred alternatives. This axis
is **Figma-only for now** — the web build ships no swap mechanism, and the web label is always
CompactBody on both presets.

---

## 4. States

Tag is non-interactive — it has no pressed, hover, or focus form. Style values are design-time
variants, not runtime states. **Standard** is the rest form of each Style.

| Style | Purpose |
|---|---|
| **Standard** | The rest form — the only interaction state. Every Style below is Standard wearing its own tokens |
| **Surface** | Neutral label on any light surface — the default |
| **Brand** | Drive-green emphasis |
| **Color** | Pastel tint for soft categorization |
| **Inverse** | For dark surfaces: dark pill flips to light content |
| **Disabled** | Ghost form: near-transparent fill, disabled content color. Visual only — Tag owns no interaction to disable |
| **Skeleton** | Loading placeholder: an **empty** `s56` × `s24` box, no label, no slots, no border |

When a product composition makes the EndSlot a remove button, that button — not the Tag — becomes
the interactive element (see § 5 and the a11y spec).

---

## 5. Animation and behavior

Source: [`tokens/rules/motion-rules.md`](https://super-dollop-pzmo65r.pages.github.io/motion.md). Tag declares no
motion tokens of its own — it consumes the DS-wide ones from `tokens/generated/motion.css`.

| Event | Token | Notes |
|---|---|---|
| Tag appears (enter) | `var(--transforming-enter-fast)` | Opacity + transform only |
| Tag disappears (exit) | `var(--transforming-exit-fast)` | Opacity + transform only |
| EndSlot remove-button press | `var(--component-push-highlight)` | Only when the slot hosts an interactive control |
| Skeleton sweep | `var(--pattern-shimmer)` | The DS shimmer: gradient of `var(--skeleton-wave)` over `var(--skeleton-on-white)` |

Reduced motion is handled at the token: `motion.css` zeroes every `--transforming-*`,
`--component-*` and `--pattern-*` inside `prefers-reduced-motion: reduce`. The component carries
no override of its own.

---

## 6. Color tokens

The three `--tag-*` custom properties are the whole color contract. Standard styles re-point
them; a Custom instance re-points them inline.

| Style | `--tag-fill` | `--tag-content` | `--tag-border` |
|---|---|---|---|
| **Surface** | `var(--background-secondary)` | `var(--text-and-icon-primary)` | `var(--border-default)` |
| **Brand** | `var(--background-brand)` | `var(--text-and-icon-always-dark)` | `var(--surface-overlay)` |
| **Color** | `var(--pastel-drive-green2)` | `var(--text-and-icon-primary)` | `var(--surface-overlay)` |
| **Inverse** | `var(--background-inverse-primary)` | `var(--text-and-icon-inverse-primary)` | `var(--surface-white-overlay)` |
| **Disabled** | `var(--surface-white-overlay)` | `var(--text-and-icon-disabled)` | `var(--surface-overlay)` |
| **Skeleton** | `var(--skeleton-on-white)` | — (no content) | — (no border) |

The border token is consumed only while `.tag--border` is present. The skeleton sweep uses
`var(--skeleton-wave)` on top of the fill.

Custom preset defaults equal the Surface row; any DS semantic token may replace any of the three.

---

## 7. Typography

| Element | Style | Tokens |
|---|---|---|
| Label | Body/Compact Body | `var(--text-body-compact-body-*)` — family + size + weight + line-height set together |
| Label (Custom, swapped — Figma-only) | Caption / PromoBody / Heading1 | The corresponding text-style group in Figma; the web label stays CompactBody |

The label color is `currentColor` from `--tag-content` — never declared on the text node.

---

## 8. Spacing

| Property | Token | Value @100% | Notes |
|---|---|---|---|
| Padding (all four sides) | `var(--sp-s4)` | 4px | Uniform — no per-side variants |
| Label side padding | `var(--sp-s2)` | 2px | On the label itself, both sides |
| Gap between parts | `var(--sp-s0)` | 0 | The label's own padding is the visual gap |
| Border radius | `var(--sp-s10)` | 10px | |
| Icon slot | `var(--sp-s16)` | 16 × 16 | Both slots |
| Height | hug | 24px | `s16` content + 2 × `s4` padding; no fixed height |
| Border | — | 1dp | Always 1dp, never scales with the SP mode; drawn inside, no layout shift |
| Skeleton box | `var(--sp-s56)` × `var(--sp-s24)` | 56 × 24 | Fixed; replaces the content box entirely |

Every SP-bound value above scales with the SP mode (100 / 130 / 150%); the 1dp border is the one
exception.

---

## 9. Usage context

### When to use Tag

- Category labels on cards, cells, or list items.
- Status badges in transaction history or trip details.
- Metadata annotations — "New", "Sale", "Verified".
- The decoration slot of a DecorateContainer composition (ServiceCard's "NEW" badge).

### When not to use Tag

- When an action is required — use a Button; Tag owns no action.
- When more than one line of text is needed — Tag is single-line only.
- When a numeric counter is the primary content — use an Indicator.

### Related components

| Component | Relation |
|---|---|
| IconContainer | The `s16` primitive both slots host |
| Indicator | Numeric counter; no text label |
| Cell | Tag can appear inside Cell slots |
| DecorateContainer | Hosts Tag as a predefined decoration |
| Button | Use when an action is required instead of a label |

---

## 10. Accessibility

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

- Non-interactive by default: no focus, no role — the label text reads as plain text. `role="img"`
  with an `aria-label` only when the Tag is a standalone status indicator whose meaning the text
  alone does not carry.
- A removable Tag exposes only the EndSlot button to assistive technology
  (`aria-label="Remove {label}"`); the minimum touch target for it is 44 × 44.
- Disabled is a visual style: no `aria-disabled` — there is no interaction to disable.
- Skeleton is hidden from assistive technology (`aria-hidden="true"`); the loading container, not
  the Tag, announces busy state.
- Icons in slots are decorative: `aria-hidden="true"`.

---

## 11. Analytics and coverage contract

| Field | Value |
|---|---|
| `data-ds-component` | `tag` |
| Coverage unit | Yes. One Tag root = one DS component instance |
| Tap target | None on the root (non-interactive); the EndSlot remove button is its own target when present |
| Root actions | none (static) / `remove` on the EndSlot button |

Required root attributes in prototypes:

```html
<span
  class="tag"
  data-ds-component="tag"
  data-ds-component-id="trip.category.tag"
  data-ds-preset="standard"
  data-ds-variant="surface">
  <span class="tag__label">Text</span>
</span>
```

Custom preset — the custom properties are the instance override, the preset is marked:

```html
<span
  class="tag"
  data-ds-component="tag"
  data-ds-preset="custom"
  style="--tag-fill: var(--safety-safety2); --tag-content: var(--text-and-icon-always-light);">
  <span class="tag__label">NEW</span>
</span>
```

Removable Tag — the button is the tracked action target:

```html
<span class="tag" data-ds-component="tag" data-ds-preset="standard" data-ds-variant="surface">
  <span class="tag__slot tag__slot--start" aria-hidden="true"><!-- icon --></span>
  <span class="tag__label">Text</span>
  <button type="button" class="tag__slot tag__slot--end"
    aria-label="Remove Text"
    data-ds-action="remove"><!-- close icon --></button>
</span>
```

---

## Changelog

| Version | Date | Change |
|---|---|---|
| 3.1.0 | 2026-09-17 | Aligned to Figma `[Tag] 3.1`: version joins the Figma line (was an independent 1.0.0). Spacing model rebuilt — uniform `s4` padding + label-owned `s2` side padding + `s0` gap replace the per-side `s6`/`s4` + `s2` gap model; height hugs (fixed `s24` dropped). Border is now off by default (`.tag--border` opt-in replaces `.tag--no-border` opt-out) and draws as an inset 1dp stroke with per-style tokens (Surface `--border-default`, Brand/Color/Disabled `--surface-overlay`, Inverse `--surface-white-overlay`). Disabled fill corrected to `--surface-white-overlay`. Skeleton is an empty `s56`×`s24` box on `--pattern-shimmer`. Presets renamed to Standard/Custom with the `--tag-*` custom-property contract; Custom adds a swappable text style. Slots renamed left/right → start/end; RTL axis documented. |
| 1.0.0 | 2026-05-19 | Initial spec: anatomy, slot structure, Standard and Custom presets, 6 states, layout tokens, RTL rules, a11y summary. |

---

# Tag — Accessibility

Static label: the core accessibility fact is that Tag is **not a control** — it reads as plain
text where it stands, takes no focus, and owns no action. The one interactive scenario is a
composition placing a remove button in the EndSlot; the button, not the tag, carries the
interaction contract. The accessible-name pattern for that button is **"Remove {tag label}"**
(e.g. "Remove Verified") — the label verbatim, no extra words.

Label rule everywhere: describe the semantic meaning, never the visual appearance.
✅ "Verified driver" · ❌ "Green tag with checkmark".

## Android · TalkBack

| Element | Label | Value | Trait | Hint |
|---|---|---|---|---|
| Tag (root, plain) | Label text, read in content order — usually as part of the containing row's description | — | None: not focusable, no role | — |
| Tag (standalone indicator) | `contentDescription` with the meaning (e.g. "Verified driver") — only when the text alone does not carry it | — | — | — |
| StartSlot / EndSlot icon | — | — | Decorative: `importantForAccessibility="no"` | — |
| Tag root (removable composition) | Tag text | — | Container of the remove button | — |
| Remove button (EndSlot) | "Remove {tag label}" | — | Button | Double-tap to activate |

- The remove button's touch target is **44 × 44 minimum**, extended past the `s16` glyph via
  hit-slop — the visual box does not grow.
- A tag inside a focusable Cell does not become its own element: the Cell takes the focus and
  the name, and the tag's text joins the Cell's description (or is excluded when redundant).

### Edge states

- **Disabled:** a visual style, not a state — **no** `disabled` semantics and no announcement
  change. TalkBack reads the dimmed label as ordinary text; nothing drops out of swipe
  navigation because nothing was navigable to begin with.
- **Skeleton:** not announced — an empty box marked `importantForAccessibility="no"`; the
  loading **container**, not the tag, carries the busy semantics.
- **RTL:** mirrored layout only — reading order and announcements are unchanged.
- **Reduced motion:** token-level — the skeleton sweep stops; nothing else changes.

---

## iOS · VoiceOver

| Element | Label | Value | Trait | Hint |
|---|---|---|---|---|
| Tag (root, plain) | Label text, read in content order | — | Static text | — |
| Tag (standalone indicator) | `accessibilityLabel` with the meaning | — | Image | — |
| StartSlot / EndSlot icon | — | — | Excluded: `isAccessibilityElement = false` | — |
| Tag root (removable composition) | Tag text | — | Container of the remove button | — |
| Remove button (EndSlot) | "Remove {tag label}" | — | Button | — |

- The remove button is its own accessibility element with the 44 × 44 minimum target
  (hit-slop past the `s16` glyph).
- Decorative tags redundant with surrounding text are excluded entirely
  (`isAccessibilityElement = false` on the root).

### Edge states

- **Disabled:** read as ordinary dimmed text — **no** "dimmed" trait: Tag is not a control, so
  announcing a disabled state would promise something to enable.
- **Skeleton:** excluded from the accessibility tree; the loading container reports busy.
- **RTL:** mirrored layout only — reading order and announcements are unchanged.
- **Reduced motion:** token-level — the skeleton sweep stops; nothing else changes.

---

## Web preview (reference)

The preview page and prototypes mark the tag up so the same contract holds:

- **Plain tag:** a `<span>` with no role, no `aria-label`, no `tabindex` — the label reads as
  text in document order. `role="img"` + `aria-label` only for a standalone indicator whose
  text alone does not carry the meaning; `role="presentation"` when fully decorative.
- **Icons:** `aria-hidden="true"` on both slot containers, always — the meaning lives in the
  label text.
- **Removable composition:**

  ```html
  <span class="tag" role="group" aria-label="Verified">
    <span class="tag__slot tag__slot--start" aria-hidden="true"><!-- icon --></span>
    <span class="tag__label">Verified</span>
    <button type="button" class="tag__slot tag__slot--end"
            aria-label="Remove Verified" data-ds-action="remove">
      <!-- close icon -->
    </button>
  </span>
  ```

  `type="button"` is required — the default `type="submit"` would submit a surrounding form in
  the filter compositions the pattern exists for. The 44 × 44 target comes from a hit-slop
  pseudo-element (`inset: calc(-1 * var(--sp-s14))` around the `s16` glyph), never from growing
  the visual box.
- **Implementation trap:** a real `<button>` synthesizes a click on <kbd>Enter</kbd> and
  <kbd>Space</kbd> — do not add a separate keydown handler, or the remove fires twice.
- **Disabled:** no `aria-disabled` — announcing "disabled" on a static label tells a
  screen-reader user there is something to enable, which is false. When a disabled Tag sits
  inside a disabled control, the control announces the state; the tag stays silent.
- **Skeleton:** `aria-hidden="true"` on the empty box; the loading **container** carries
  `aria-busy="true"`:

  ```html
  <div aria-busy="true">
    <span class="tag tag--skeleton" aria-hidden="true"></span>
  </div>
  ```

- **Reduced motion:** handled at the token — `tokens/generated/motion.css` zeroes
  `--transforming-enter-fast`, `--transforming-exit-fast`, `--component-push-highlight` and
  `--pattern-shimmer` inside `prefers-reduced-motion: reduce`; the component ships no override
  of its own.

### Colour and contrast

Text and icon pairings against each style's fill, WCAG 2.1 AA target 4.5:1:

| Style | Content token | Fill token | Requirement |
|---|---|---|---|
| surface | `--text-and-icon-primary` | `--background-secondary` | 4.5:1 |
| brand | `--text-and-icon-always-dark` | `--background-brand` | 4.5:1 |
| color | `--text-and-icon-primary` | `--pastel-drive-green2` | 4.5:1 |
| inverse | `--text-and-icon-inverse-primary` | `--background-inverse-primary` | 4.5:1 |
| disabled | `--text-and-icon-disabled` | `--surface-white-overlay` | Not required — non-interactive dimmed content |

The disabled fill is a near-transparent overlay: the ghost form is carried by the dimmed text
alone. The border (`tag--border`) is decorative and carries no contrast requirement. Never rely
on the fill colour alone to convey meaning — the label text is the meaning.

---

## Machine contract — `specs/components/tag/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": "tag",
  "name": "Tag",
  "version": "3.1.0",
  "description": "A compact static inline label for categorization, status, and metadata. One size, hugging both axes; non-interactive by contract.",
  "files": {
    "spec": "specs/components/tag/tag.md",
    "a11y": "specs/components/tag/tag-a11y.md",
    "preview": "src/tag.njk",
    "css": "src/shared/shared.css"
  },
  "figma": {
    "library": "🕹️ Oymyakon 3.30.1 (components)",
    "fileKey": "7vdl5YkZFDWvh9QvSmydsH",
    "componentSets": {
      "[Tag] 3.1": {
        "key": "c5f29533d63451d269ecc31e86dede90b0371e98",
        "note": "The wrapper component (node 4081:5179): one INSTANCE_SWAP prop `Preset` choosing StandardTag or CustomTag."
      },
      "StandardTag": {
        "key": "632d5c542195286450ee97c0baf5652fc7418b6f",
        "note": "Style × RTL variants + StartSlot/EndSlot/Border booleans."
      },
      "CustomTag": {
        "key": "b70c0ebbde2cb92965eefbfc0582305586d2bdfa",
        "note": "State variants + IconSlot#1/#2/Border booleans; free colour tokens and a swappable label text style."
      },
      "Textcontainer": {
        "key": "d43c1b100b2cfe987a0d8c53435044bc53712a27",
        "note": "The label primitive carrying the s2 side padding."
      }
    },
    "capturedAt": "2026-09-17"
  },
  "root": {
    "class": "tag",
    "dataDsComponent": "tag"
  },
  "anatomy": {
    "startSlot": {
      "class": "tag__slot--start",
      "optional": true,
      "notes": "IconContainer s16, leading. Hidden by default on the web (the Figma master shows it)."
    },
    "label": {
      "class": "tag__label",
      "notes": "The required single-line text. Carries its own s2 side padding (Figma's TextContainer) — the visual gap to icons and edges comes from here, the row gap is s0."
    },
    "endSlot": {
      "class": "tag__slot--end",
      "optional": true,
      "notes": "IconContainer s16, trailing. Hosts the remove <button> when a composition needs one — the button, not the tag, is the interactive element."
    }
  },
  "axes": {
    "preset": {
      "title": "Preset",
      "type": "enum",
      "values": [
        "standard",
        "custom"
      ],
      "default": "standard",
      "css": {
        "mechanism": "standard = the style modifier classes with fixed tokens; custom = the same markup with --tag-fill / --tag-content / --tag-border re-pointed inline on the instance"
      },
      "figma": {
        "kind": "component-set",
        "values": {
          "standard": "StandardTag",
          "custom": "CustomTag"
        }
      }
    },
    "style": {
      "title": "Style",
      "type": "enum",
      "values": [
        "surface",
        "brand",
        "color",
        "inverse",
        "disabled",
        "skeleton"
      ],
      "default": "surface",
      "css": {
        "modifierTemplate": ".tag--{value}",
        "mechanism": "surface is the bare .tag; each modifier re-points the three --tag-* custom properties"
      },
      "figma": {
        "kind": "variant-property",
        "property": "Style"
      },
      "notes": "Standard preset only. Fixed token trios per value — see the spec § 6."
    },
    "state": {
      "title": "State",
      "type": "enum",
      "values": [
        "default",
        "skeleton"
      ],
      "default": "default",
      "css": {
        "modifier": ".tag--skeleton"
      },
      "figma": {
        "kind": "variant-property",
        "property": "State"
      },
      "notes": "Custom preset only; skeleton is the same .tag--skeleton the Standard preset uses."
    },
    "startSlot": {
      "title": "StartSlot",
      "type": "boolean",
      "default": false,
      "css": {
        "mechanism": "presence of the .tag__slot--start element"
      },
      "figma": {
        "kind": "variant-property",
        "property": "🔴StartSlot",
        "notes": "A boolean component property, not a variant; the Figma master defaults it to true."
      }
    },
    "endSlot": {
      "title": "EndSlot",
      "type": "boolean",
      "default": false,
      "css": {
        "mechanism": "presence of the .tag__slot--end element"
      },
      "figma": {
        "kind": "variant-property",
        "property": "🔵EndSlot",
        "notes": "A boolean component property, not a variant; the Figma master defaults it to true."
      }
    },
    "border": {
      "title": "Border",
      "type": "boolean",
      "default": false,
      "css": {
        "modifier": ".tag--border"
      },
      "figma": {
        "kind": "variant-property",
        "property": "Border",
        "notes": "A boolean component property toggling the Border layer; default false in Figma too."
      }
    },
    "fill": {
      "title": "Fill (Custom)",
      "type": "token",
      "customizable": "any DS semantic colour token",
      "default": "--background-secondary",
      "css": {
        "customProperty": "--tag-fill"
      },
      "figma": {
        "kind": "none",
        "notes": "CustomTag: the container fill is unlocked on the instance."
      },
      "tokens": [
        "--background-secondary",
        "--background-brand",
        "--pastel-drive-green2",
        "--background-inverse-primary",
        "--surface-white-overlay",
        "--skeleton-on-white",
        "--safety-safety2"
      ]
    },
    "content": {
      "title": "Content (Custom)",
      "type": "token",
      "customizable": "any DS semantic colour token",
      "default": "--text-and-icon-primary",
      "css": {
        "customProperty": "--tag-content"
      },
      "figma": {
        "kind": "none",
        "notes": "CustomTag: label and icons take the colour via currentColor."
      },
      "tokens": [
        "--text-and-icon-primary",
        "--text-and-icon-always-dark",
        "--text-and-icon-inverse-primary",
        "--text-and-icon-disabled",
        "--text-and-icon-always-light"
      ]
    },
    "borderColor": {
      "title": "Border colour (Custom)",
      "type": "token",
      "customizable": "any DS semantic colour token",
      "default": "--border-default",
      "css": {
        "customProperty": "--tag-border"
      },
      "figma": {
        "kind": "none",
        "notes": "Consumed only while border=true."
      },
      "tokens": [
        "--border-default",
        "--surface-overlay",
        "--surface-white-overlay"
      ]
    },
    "textStyle": {
      "title": "Label text style (Custom)",
      "type": "enum",
      "values": [
        "compact-body",
        "caption",
        "promo-body",
        "heading1"
      ],
      "default": "compact-body",
      "figma": {
        "kind": "none",
        "notes": "Figma-only for now: an INSTANCE_SWAP on the 🟡StartText slot (CompactBody default; Caption / PromoBody / Heading1 preferred). Not the set's `Style` variant property — that is the colour axis. The web build ships no swap mechanism: the web label is always CompactBody."
      }
    }
  },
  "states": {
    "static": [
      "standard"
    ]
  },
  "constraints": [
    "Non-interactive: no pressed, hover, focus, or disabled interaction form — the disabled style is visual only, and the EndSlot remove <button> is the only sanctioned control.",
    "style applies only when preset=standard; state, fill, content, borderColor and textStyle apply only when preset=custom.",
    "Width and height hug content — never set a fixed size; skeleton is the one exception: an empty s56 × s24 box with no label, slots, or border.",
    "The border is an inset 1dp stroke: it never affects layout, and 1dp never scales with the SP mode.",
    "The label is single line; multi-line copy and numeric counters are out of contract.",
    "Padding is uniform s4 with row gap s0 — per-side padding switches are out of contract; the label's own s2 side padding makes the gaps.",
    "No motion tokens are declared on the component; enter/exit (Transforming Fast) belongs to the mounting composition and the skeleton sweep consumes the DS-wide --pattern-shimmer."
  ],
  "analytics": {
    "dataDsComponent": "tag",
    "actions": [
      "remove"
    ],
    "targets": [
      "the EndSlot <button> (data-ds-action=\"remove\") — the root itself is never a tap target"
    ],
    "notes": "data-ds-preset carries standard|custom; data-ds-variant carries the Style value on the standard preset."
  },
  "rtl": {
    "supported": true,
    "notes": "dir=\"rtl\" reverses the flex row (StartSlot renders trailing); geometry is symmetric so nothing else mirrors. Non-directional glyphs stay upright; directional icons mirror per the icon's own RTL variant. The Figma set carries explicit RTL=On variants."
  }
}
```
