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

---

# Cell · Oymyakon DS 3

> List row — a universal clickable element for displaying structured content in vertical lists.

**Version:** 3.4.7 · **Status:** Draft · **Figma line:** `[Cell] 3.4`

---

## 1. Overview

Cell is the fundamental building block for lists. Used wherever a set of homogeneous items needs to be displayed: settings, menu items, transactions, addresses, contacts.

Cell always occupies the full width of its container. Height is determined by content (hug), minimum height — s48.

All code presets (Simple-cell, Icon-cell, Squircle-cell) are based on a single unified **CustomCell** — the base `.cell` component with all slots configurable. Predefined presets are locked configurations of CustomCell.

---

## 2. Slot structure

Every named element of Cell is an instance of [`slot.md`](https://super-dollop-pzmo65r.pages.github.io/slot.md) with preset properties (width, height, align). A slot is invisible by itself: no background, border, or shadow. It only controls the size and alignment of the nested component.

```
Cell
├── topContainer        container (flex row) — wraps all horizontal slots
│                         align: top-left · CSS class .cell-top-content
│     ├── start-slot    slot › IconContainer
│     │                   width: s40 · height: hug (min s36) · align: center / center
│     │
│     ├── start-text    slot › TextRow stack
│     │     width: fill · height: hug (min s36) · align-v: center
│     │     ├── top-row       TextRow   (title, always visible)
│     │     ├── middle-row    TextRow   (subtitle, hidden by default)
│     │     └── bottom-row    TextRow   (additional, hidden by default)
│     │
│     ├── end-text      slot › TextRow stack
│     │     width: hug · height: hug (min s36) · align-v: center · default config: standard
│     │     ├── top-row       TextRow   (title, always visible)
│     │     ├── middle-row    TextRow   (subtitle, hidden by default)
│     │     └── bottom-row    TextRow   (additional, hidden by default)
│     │
│     └── end-slot      slot › 1 or 2 items (IconContainer / Badge / Switch / Value / Chevron)
│                         width: hug · height: hug (min s36) · align: center / center · gap s8
│
└── descriptionContainer   container below topContainer (hidden by default, shown via boolean)
                          CSS class .cell-description
      ├── divider       slot › Divider (optional)
      └── text-row      TextRow   (5 states: default / error / success / inform / disabled)
```

**HTML structure:**
```html
<button class="cell" data-ds-component="cell">
  <div class="cell-top-content">
    <!-- start-slot, cell-start-text, cell-end-slot go here -->
  </div>
  <div class="cell-description">
    <!-- optional: divider + text -->
  </div>
</button>
```

> `cell-description` is always present in the DOM. Shown by adding `.is-visible`. Hidden by default (`display: none`).

> Full Slot specification — [`specs/primitives/slot.md`](https://super-dollop-pzmo65r.pages.github.io/slot.md).

---

## 3. Anatomy (diagram)

```
┌────────────────────────────────────────────────────────────────────────────────┐
│  [start-slot]  [start-text]                  [end-text]  [end-slot]           │
│                [s1][s2] title-text [s3][s4]  [s1][s2] title-text [s3][s4]    │
│                [s1][s2] subtitle-text [s3][s4]  [s1][s2] subtitle-text [s3][s4]  (opt.) │
│                [s1][s2] additional-text [s3][s4]  [s1][s2] additional-text [s3][s4]  (opt.) │
└────────────────────────────────────────────────────────────────────────────────┘
```

| Slot | Type | Required | Description |
|---|---|---|---|
| **start-slot** | IconContainer (default), or hidden | Optional | Left slot. Sets visual context. Hidden by default option available |
| **start-text** | top-row + middle-row + bottom-row | Required | Main text area. Fills remaining space, rows centred vertically; truncates with ellipsis when end-text is present |
| **end-text** | top-row + middle-row + bottom-row | Optional | Secondary text area. Hug width. Higher priority than start-text — never truncated. Hidden by default |
| **end-slot** | Icon, Badge, Text-value, Switch, Chevron, or none | Optional | Right slot. Action or value |
| **divider** | Divider | Optional | Bottom border. Aligns to start-slot or full-bleed |

---

## 4. Layout & Spacing

Cell uses a horizontal autolayout with top-left alignment.

> The right side (end-slot) has priority by default, but can be repositioned to the left if needed.

```
padding-top:    var(--sp-s10)
padding-bottom: var(--sp-s10)
padding-left:   var(--sp-s16)
padding-right:  var(--sp-s16)
min-height:     var(--sp-s48)
height:         hug (grows with content)

gap (start-slot → start-text): var(--sp-s8)
gap (start-text → end-text):   var(--sp-s8)
gap (end-text → end-slot):     var(--sp-s8)
```

Every gap between slots is `var(--sp-s8)` — including the gap between the two items an `end-slot`
can hold (§8). The gaps are base values on `.cell-top-content` and `.cell-end-slot`, not preset
overrides: every preset inherits them.

### Containers

Cell is two stacked containers:

| Container | Role | CSS class |
|---|---|---|
| **topContainer** | The horizontal row — start-slot, start-text, end-text, end-slot | `.cell-top-content` |
| **descriptionContainer** | The optional block below the row — divider + text | `.cell-description` |

`topContainer` aligns its content to the **top-left** edge: the slots sit on the row's top edge and
grow downwards, each centring its own content inside itself.

### Slot sizing inside topContainer

Every slot stands on the same `var(--sp-s36)` floor and hugs its content above it. The floor is a
base value — a preset may enlarge a slot (the Squircle sizes do), never lower it.

| Slot | Width | Height | Content alignment |
|---|---|---|---|
| **start-slot** | `var(--sp-s40)` | hug, min `var(--sp-s36)` | centred both axes |
| **start-text** | fill | hug, min `var(--sp-s36)` | centred vertically, text left-aligned |
| **end-text** | hug | hug, min `var(--sp-s36)` | centred vertically, text right-aligned |
| **end-slot** | hug | hug, min `var(--sp-s36)` | centred both axes — chevron by default |

---

## 5. Start-slot

IconContainer placed by default. Can be hidden. Width `var(--sp-s40)`, height hugs the content from a
`var(--sp-s36)` floor. Placed elements are centred horizontally and vertically within the slot.

| Variant | Description |
|---|---|
| `icon` | IconContainer — default |
| `box` | `.cell-start-slot--box` modifier — the slot gets a boxed look: radius `var(--sp-s8)`, background `var(--surface-on-white)`, border `var(--border-default)`, icon colour `var(--text-and-icon-secondary)` |
| `hidden` | No left element; start-text aligns to left padding |

---

## 6. Start-text

Vertical autolayout. Fills the remaining horizontal space; height hugs the rows from a `var(--sp-s36)`
floor, with the rows centred vertically inside it. Displays 1, 2, or 3 rows. Truncates with ellipsis when
end-text is present and space is insufficient.

### Row visibility

| Configuration | Visible rows |
|---|---|
| Title only | top-row |
| Title + Subtitle | top-row + middle-row |
| Title + Subtitle + Additional | top-row + middle-row + bottom-row |

### Text config (applies to every text row)

Each row — top-row, middle-row, bottom-row — shares the same text config structure.

**Multiline**

| Value | Behaviour |
|---|---|
| `off` (default) | Single line, text truncates with ellipsis |
| `on` | Text wraps to multiple lines. When `on`, slot-end-1 and slot-end-2 are hidden (no room for end slots in multiline mode) |

**Config mode**

| Value | Behaviour |
|---|---|
| `standard` | Text only, no extra slots visible |
| `expanded` | Up to 4 additional slots around the text: slot-start-1, slot-start-2 (left), slot-end-1, slot-end-2 (right). Each slot is hug autolayout, hidden by default. IconContainer placed by default inside each slot |

---

### top-row (title)

Horizontal autolayout, hug height.

```
[slot-start-1] [slot-start-2] [title-text] [slot-end-1] [slot-end-2]
```

- **title-text** — default typography: `var(--text-body-main-body-*)`, color: `var(--text-and-icon-primary)`. Can be changed to any Oymyakon DS text style.
- **slot-start-1, slot-start-2** — visible only in `expanded` config. Hidden by default.
- **slot-end-1, slot-end-2** — visible only in `expanded` config; hidden when `multiline: on`. Hidden by default.

### middle-row (subtitle)

Horizontal autolayout, hug height. Hidden by default.

```
[slot-start-1] [slot-start-2] [subtitle-text] [slot-end-1] [slot-end-2]
```

- **subtitle-text** — default typography: `var(--text-body-compact-body-*)`, color: `var(--text-and-icon-secondary)`. Can be changed to any Oymyakon DS text style.
- **slot-start-1, slot-start-2** — visible only in `expanded` config. Hidden by default.
- **slot-end-1, slot-end-2** — visible only in `expanded` config; hidden when `multiline: on`. Hidden by default.

### bottom-row (additional)

Horizontal autolayout, hug height. Hidden by default.

```
[slot-start-1] [slot-start-2] [additional-text] [slot-end-1] [slot-end-2]
```

- **additional-text** — default typography: `var(--text-body-compact-body-*)`, color: `var(--text-and-icon-secondary)`. Can be changed to any Oymyakon DS text style.
- **slot-start-1, slot-start-2** — visible only in `expanded` config. Hidden by default.
- **slot-end-1, slot-end-2** — visible only in `expanded` config; hidden when `multiline: on`. Hidden by default.

---

## 7. End-text

Optional secondary text area. Hidden by default. Hug on both axes from a `var(--sp-s36)` height floor, rows
centred vertically — it shrinks to its content and never truncates. Has higher layout priority than start-text: when both are visible, start-text fills remaining space and truncates first.

Default text config: `standard` (no expanded slots).

### Row visibility

Same rules as start-text:

| Configuration | Visible rows |
|---|---|
| Title only | top-row |
| Title + Subtitle | top-row + middle-row |
| Title + Subtitle + Additional | top-row + middle-row + bottom-row |

### Text config (applies to every text row)

**Multiline**

| Value | Behaviour |
|---|---|
| `off` (default) | Single line, text truncates with ellipsis |
| `on` | Text wraps to multiple lines. When `on`, slot-end-1 and slot-end-2 are hidden |

**Config mode**

| Value | Behaviour |
|---|---|
| `standard` (default) | Text only, no extra slots visible |
| `expanded` | Up to 4 additional slots around the text: slot-start-1, slot-start-2 (left), slot-end-1, slot-end-2 (right). Each slot is hug autolayout, hidden by default. IconContainer placed by default inside each slot |

### top-row (title)

```
[slot-start-1] [slot-start-2] [title-text] [slot-end-1] [slot-end-2]
```

- **title-text** — default typography: `var(--text-body-main-body-*)`, color: `var(--text-and-icon-primary)`. Can be changed to any Oymyakon DS text style.

### middle-row (subtitle)

Hidden by default.

```
[slot-start-1] [slot-start-2] [subtitle-text] [slot-end-1] [slot-end-2]
```

- **subtitle-text** — default typography: `var(--text-body-compact-body-*)`, color: `var(--text-and-icon-secondary)`.

### bottom-row (additional)

Hidden by default.

```
[slot-start-1] [slot-start-2] [additional-text] [slot-end-1] [slot-end-2]
```

- **additional-text** — default typography: `var(--text-body-compact-body-*)`, color: `var(--text-and-icon-secondary)`.

---

## 8. End-slot

The end-slot is a single hug container at the right edge of `topContainer`. Its **slot count** is a
variation of the slot itself: it holds either one item or two, never a second independent slot.

| Slot count | Content |
|---|---|
| `1` (default) | One item — chevron, value, badge, switch or icon |
| `2` | Two items side by side, `var(--sp-s8)` apart, hugging as one group |

With two items the group still hugs and still sits `var(--sp-s8)` from end-text, so the right edge
keeps the cell's `var(--sp-s16)` padding regardless of the count. Order reads left to right: the
first item is closer to end-text, the second sits at the edge.

### End-slot variants

Each item takes one of these variants:

| Variant | Description |
|---|---|
| `none` | No right element |
| `chevron` | IconContainer with `cell-chevron`, `rtl: true` — indicates navigation. Only end-slot content with RTL |
| `value` | Text value (e.g. "English", "On") |
| `badge` | Numeric Badge or dot indicator |
| `switch` | Switch component — inline toggle |
| `icon` | IconContainer with any utility icon (delete, share, etc.), `rtl: false` |

Any combination of two variants is valid — specific pairings will be formalized as presets once
common configurations are identified. A chevron, when present, is the item at the edge.

> **Figma status (2026-08-06).** The masters read on that date — `[CustomCell] 3.3` and the preset
> sets `[SimpleCell]` / `[IconCell]` / `[SquircleCell]` 3.4 — carry a single `EndSlot` layer with no
> count choice. The variation is the agreed contract and the design catches up at the next update, so
> a Figma read that finds no choice is the design lagging, not this section being wrong.

---

## 9. Description slot

Optional area below the main cell body (below start-text / end-text). Hidden by default.

```
┌──────────────────────────────────────────────────────────────────┐
│  [start-slot]  [start-text]         [end-text]      [end-slot]  │
│  ──────────────────────────────────────────────────────────────  │
│  [description]                                                  │
└──────────────────────────────────────────────────────────────────┘
```

### Configs

| Config | Description |
|---|---|
| `default` | Divider above + description text below |
| `only text` | Description text only, no divider |
| `only divider` | Divider only, no text |

### Description text config

Same text config as middle-slot rows: `multiline` (off / on) + `standard` / `expanded` mode with up to 4 slots around the text.

Default typography: `var(--text-body-compact-body-*)`, color: `var(--text-and-icon-secondary)`.

### States

| State | Visual |
|---|---|
| `default` | Standard appearance |
| `error` | Text color: error semantic token |
| `success` | Text color: success semantic token |
| `warning` | Text color: warning semantic token |
| `inform` | Text color: accent/inform semantic token |
| `disabled` | Text color: `var(--text-and-icon-disabled)` |

### Divider

- Color: `var(--border-default)`
- With start-slot: left offset = `var(--sp-s8) + start-slot-width + var(--sp-s16)` (aligns to text)
- Without start-slot: full-bleed

---

## 10. States

| State | Visual | Interaction |
|---|---|---|
| **Default** | Background `var(--background-primary)` | Available |
| **Pressed** | Background `Surface/PressedOverlay` | On touch/click |
| **Disabled** | Opacity 40%, background unchanged | Not interactive |
| **Selected** | Background `Background/Brand` or checkmark in end-slot | Multi-select |

### Disabled Rule

Disabled Cell does not respond to press and shows no animation. A Switch in end-slot carries its own disabled appearance.

---

## 11. Animation & Behavior

Source: [`motion-rules.md`](https://super-dollop-pzmo65r.pages.github.io/motion.md) — section 6.1 "Press animation", pattern `pushHighlight`.

### 11.1 Why `pushHighlight` and not `pushItem`

Cell is a list row. It does not scale on press because:
- Scaling a row deforms adjacent items.
- Color feedback is sufficient for this pattern.
- On Android: ripple; on iOS: background color change; on web: `background-color`.

### 11.2 Animation Table

| Event | Pattern | Token | Duration | Curve | CSS |
|---|---|---|---|---|---|
| Press (touch down) | `pushHighlight` | `--component-push-highlight` | 150ms | `linear` | `transition: background-color 150ms cubic-bezier(0.25, 0.25, 0.75, 0.75)` |
| Release (touch up) | `pushHighlight` | `--component-push-highlight` | 150ms | `linear` | same |
| Default → Disabled | `Transforming/State/Default` | `--transforming-state-default` | 200ms | `slow-ease-out` | `transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1)` |
| Disabled → Default | `Transforming/State/Default` | `--transforming-state-default` | 200ms | `slow-ease-out` | same |
| Default → Selected | `Transforming/State/Default` | `--transforming-state-default` | 200ms | `slow-ease-out` | `transition: background-color 200ms cubic-bezier(0.4, 0, 0.2, 1)` |

### 11.3 CSS Implementation

```css
.cell {
  transition: background-color var(--component-push-highlight);
  background-color: transparent;
}

.cell:active {
  background-color: var(--color-surface-pressed-overlay);
}

.cell[disabled] {
  opacity: 0.4;
  pointer-events: none;
  transition: opacity var(--transforming-state-default);
}

.cell--selected {
  background-color: var(--color-background-brand);
  transition: background-color var(--transforming-state-default);
}
```

### 11.4 Platform Specifics

| Platform | Press implementation |
|---|---|
| **Web** | `background-color` via `--component-push-highlight` |
| **Android** | Ripple over cell background (native Material ripple) |
| **iOS** | `UITableViewCell` highlight color — `backgroundColor` change |
| **Flutter** | `InkWell` with custom `splashColor` from `Surface/PressedOverlay` |

### 11.5 Reduced Motion

```css
@media (prefers-reduced-motion: reduce) {
  .cell {
    transition: none;
  }
}
```

All transitions are disabled under `prefers-reduced-motion: reduce`. State changes (pressed color, disabled opacity) happen instantly.

---

## 12. Color Tokens

| Element | Token |
|---|---|
| Background Default | `var(--background-primary)` |
| Background Pressed | `Surface/PressedOverlay` |
| Background Selected | `Background/Brand` |
| start-text title-text | `var(--text-and-icon-primary)` |
| start-text subtitle-text | `var(--text-and-icon-secondary)` |
| start-text additional-text | `var(--text-and-icon-secondary)` |
| end-text title-text | `var(--text-and-icon-primary)` |
| end-text subtitle-text | `var(--text-and-icon-secondary)` |
| end-text additional-text | `var(--text-and-icon-secondary)` |
| start-slot icon | `var(--text-and-icon-primary)` |
| end-slot chevron | `var(--text-and-icon-secondary)` |
| end-slot value | `var(--text-and-icon-secondary)` |
| Divider | `var(--border-default)` |
| All elements (Disabled) | opacity 40% applied on top |

---

## 13. Typography

| Element | Default style | Token | Color |
|---|---|---|---|
| start-text title-text | Main Body | `var(--text-body-main-body-*)` | `var(--text-and-icon-primary)` |
| start-text subtitle-text | Compact Body | `var(--text-body-compact-body-*)` | `var(--text-and-icon-secondary)` |
| start-text additional-text | Compact Body | `var(--text-body-compact-body-*)` | `var(--text-and-icon-secondary)` |
| end-text title-text | Main Body | `var(--text-body-main-body-*)` | `var(--text-and-icon-primary)` |
| end-text subtitle-text | Compact Body | `var(--text-body-compact-body-*)` | `var(--text-and-icon-secondary)` |
| end-text additional-text | Compact Body | `var(--text-body-compact-body-*)` | `var(--text-and-icon-secondary)` |
| description-text | Compact Body | `var(--text-body-compact-body-*)` | `var(--text-and-icon-secondary)` |
| end-slot value | Compact Body | `var(--text-body-compact-body-*)` | `var(--text-and-icon-secondary)` |

Any row text style can be overridden with any Oymyakon DS typography token.

---

## 14. Usage Context

### When to Use Cell

- Settings lists (profile, app preferences).
- Contacts, drivers, passengers.
- Trip history, transactions.
- Menus: payment method, categories, order options.
- Address books.

### When Not to Use Cell

- When a cover-image card is needed — use Card.
- When multi-line rich text is required — use a dedicated component.
- When multiple actions are needed — consider SwipeCell or a context menu.

### Related Components

| Component | Relation |
|---|---|
| List | Wrapper for a set of Cells |
| IconContainer | start-slot and end-slot |
| Avatar | start-slot |
| Badge | end-slot |
| Switch | end-slot (inline toggle) |
| Divider | separator between cells |
| Bottom Sheet | Cell used inside as menu items |

---

## 15. Skeleton

Hidden loading state for the entire cell. Replaces all content with placeholder shapes.

| Element | Skeleton shape |
|---|---|
| start-slot | Rounded rectangle, same size as IconContainer |
| title-text | Rectangle, ~60–80% of available width, height matches line-height |
| subtitle-text | Rectangle, ~40–60% of available width (if subtitle visible) |
| end-slot | Hidden |

Skeleton appearance is identical across all presets (Simple / Icon / Squircle / Custom). Motion follows the standard shimmer animation from the DS.

---

## 16. Predefined presets

All three named presets are **locked contracts from the design system**. Teams cannot modify slot contents, gap, colors, or structure inside a predefined preset — they use it as-is.

| Preset | Modifier | `data-ds-preset` | Description | Locked properties |
|---|---|---|---|---|
| **Simple-cell** | `.cell--simple` | `simple` | No start slot. Title + optional subtitle, optional end-text, chevron. | All default Cell tokens |
| **Icon-cell** | `.cell--icon` | `icon` | Start slot: plain DS icon 24×24, `--text-and-icon-primary`. | Icon size, icon color |
| **Squircle-cell** | `.cell--squircle-s/m/l` | `squircle-s` / `squircle-m` / `squircle-l` | Start slot: Squircle component. 3 sizes (§16). | Squircle size, chevron color |

### Slot height constraints

Slot heights are a base contract, not a per-preset setting: every slot in `topContainer` stands on a
`var(--sp-s36)` floor and hugs its content above it, so a cell grows naturally from one line to two
while each slot keeps its content optically centred (§4). A preset may enlarge a slot — the Squircle
sizes below do — but never lowers the floor or caps the hug.

### Squircle-cell sizes

Verified against Figma `[SquircleCell] 3.4` (nodes `4831:5607` S · `4995:63644` M · `4959:45119` L).
The start-slot is fixed at exactly the Squircle's size; the preset size names map 1:1 onto the
Squircle component's own size scale.

| Preset size | Squircle modifier | Squircle dimensions | Start-slot size | Cell modifier | Two-row cell height |
|---|---|---|---|---|---|
| **S** | `.squircle--s` | 40×40, border-radius `var(--sp-s16)` | 40×40 | `.cell--squircle-s` | 60 |
| **M** | `.squircle--m` | 48×48, border-radius `var(--sp-s16)` | 48×48 | `.cell--squircle-m` | 64 |
| **L** | `.squircle--l` | 56×56, border-radius `var(--sp-s20)` | 56×56 | `.cell--squircle-l` | 76 |

> Start-slot size tracks the Squircle size exactly — no additional padding added by the slot.

> **M vertical padding.** The M size locks the top-content vertical padding to `var(--sp-s8)`
> (S and L keep the default `var(--sp-s10)`), which is what lands the two-row M cell on 64.

> **EndSlot in all predefined presets:** chevron icon, color `var(--text-and-icon-primary)`. Locked — cannot be changed.

> **StartText, EndText** in Icon-cell and Squircle-cell: same tokens as Simple-cell. Not locked — teams populate with their content.

> Squircle is its own DS component — sizes, radii and style presets live in
> [`squircle.md`](https://super-dollop-pzmo65r.pages.github.io/squircle.md); Cell only sizes the slot around it.

---

## 17. Card-wrap pattern

Any Cell preset can be visually presented as a button by wrapping it in a container with rounded corners, a background color, and horizontal padding. The animation (press state) is clipped to the visual boundary of the container via `overflow: hidden`.

```html
<div class="cell-card-wrap">
  <button class="cell cell--simple" data-ds-component="cell" data-ds-preset="simple">
    ...
  </button>
</div>
```

```css
.cell-card-wrap {
  border-radius: var(--sp-s16);
  overflow: hidden;                        /* clips press animation to rounded boundary */
  margin: 0 var(--sp-s16);                 /* horizontal inset from screen edge */
  background: var(--background-secondary); /* or any DS background token */
}
```

**Rules:**
- `overflow: hidden` is mandatory — without it the press ripple escapes the rounded corners.
- Background color is set on the wrapper, not on `.cell` itself.
- Horizontal margin (inset) is set on the wrapper. Cell padding remains `var(--sp-s16)`.
- Any preset (Simple, Icon, Squircle) can be used inside the wrapper.

---

## 18. Accessibility

Brief summary. Full spec: `specs/cell-a11y.md` (in progress).

- Cell must have a meaningful `accessibilityLabel` including title and subtitle.
- If end-slot is chevron (navigation), add hint: "Tap to open".
- If end-slot is Switch, Switch is read as a separate element.
- Disabled Cell: trait `dimmed` (iOS) / `disabled` (Android), no hint.
- Selected Cell: trait `selected`.

---

## 19. Analytics and coverage contract

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

| Field | Value |
|---|---|
| `data-ds-component` | `Cell` |
| Coverage unit | Yes. One Cell root counts as one DS component instance |
| Tap target model | Root target by default. Switch or other interactive end-slot is a separate nested component target |
| Root actions | `tap`, `open`, `select` |
| Internal targets | `end-slot` only when the end-slot is an independent action and not a nested DS component |
| Emits value | Optional: use `data-ds-value` for selected menu value or current setting |

Required root attributes in prototypes:

```html
<button
  class="cell"
  data-ds-component="Cell"
  data-ds-component-id="settings.language.cell"
  data-ds-variant="navigation"
  data-ds-state="default"
  data-ds-action="open">
  ...
</button>
```

If the end-slot is a Switch, the Switch is its own DS component and owns `data-ds-action="toggle"`. The Cell root still counts as a coverage unit, but the tap event belongs to the Switch when the user taps the switch target.

Disabled Cell instances keep `data-ds-component="Cell"` for coverage, set `data-ds-state="disabled"`, and do not emit tap analytics.

---

## RTL

**Default: RTL = Off.**

| Element | RTL behaviour |
|---|---|
| Cell root | `dir="rtl"` reverses the horizontal slot order: end-slot appears on the left, start-slot on the right |
| start-slot IconContainer | `rtl: false` — does not flip |
| end-slot `chevron` | `rtl: true` — mirrors horizontally (points left in LTR → points right in RTL) |
| end-slot `icon` | `rtl: false` — does not flip |
| end-slot `switch`, `badge`, `value` | No flip; position mirrors with layout direction |
| Text alignment | Follows `dir` attribute automatically |

To enable RTL on a Cell instance, set `dir="rtl"` on the root element. No additional CSS is required — the flex row reverses automatically.

---

## Changelog

Rows dated before 2026-08-06 come from an independent repo counter that had reached 3.11.1; the
component was realigned to the Figma line `[Cell] 3.4` on that date. A number lower than the row
above it is that realignment, not a regression.

| Version | Date | Change |
|---|---|---|
| 3.4.7 | 2026-08-06 | §8 remodelled (owner): the end-slot is **one** container with a **slot-count variation** — one item or two, `var(--sp-s8)` apart, hugging as a group — instead of two independent slots with a hidden second one. `capabilities.json` follows: the `endSlot2` boolean is replaced by `endSlotCount` (`1` / `2`, default `1`). Geometry is unchanged, so the web build needed no CSS change; the preview cover demo now shows the two items inside a single end-slot. |
| 3.4.6 | 2026-08-06 | Dual end-slot confirmed as the contract (owner) with the Figma lag recorded in §8: the masters carry one `EndSlot` layer today. `capabilities.json` corrected — `endSlot` and `endSlot2` were bound as Figma **variant properties** `End-slot` / `End-slot-2`, which exist nowhere: `[CustomCell] 3.3` is a plain component with no variants at all, and the preset sets expose only `size` / `State` / `RTL`. Both axes are now bound as layer visibility, and the `endSlot2` gap text follows 3.4.5 at `var(--sp-s8)`. |
| 3.4.5 | 2026-08-06 | The last `var(--sp-s4)` is gone (owner): two elements stacked inside one end-slot (§8, end-slot-1 → end-slot-2) also stand `var(--sp-s8)` apart, so `var(--sp-s8)` is now the single gap value everywhere inside the cell. |
| 3.4.4 | 2026-08-06 | End-slot width becomes hug with no floor (owner) — it takes exactly its content, so a chevron sits `var(--sp-s16)` from the cell edge again instead of centred in a `var(--sp-s40)` box. All three gaps between slots are now `var(--sp-s8)`; the `var(--sp-s4)` that used to sit before the end-slot now only applies between two elements stacked inside it. |
| 3.4.3 | 2026-08-06 | Container and slot-sizing contract stated (owner): the row is **topContainer** and the block below it **descriptionContainer** (§2, §4); `topContainer` aligns its content top-left; every slot stands on a `var(--sp-s36)` floor and hugs above it — start-slot and end-slot `var(--sp-s40)` wide with content centred, start-text fill, end-text hug, both centring their rows vertically. The per-preset slot min/max pairs are gone: Simple-cell had floored its end-slot at `var(--sp-s28)`, below the base, and Icon-cell only restated `var(--sp-s36)` while capping hug at `var(--sp-s40)`. shared.css matches. |
| 3.4.2 | 2026-08-06 | §4 gaps corrected to the shipped build and to Figma: start-slot → start-text and start-text → end-text are `var(--sp-s8)`, only end-text → end-slot is `var(--sp-s4)`. §4 had listed `s4` for all three since 2026-04-28 while `.cell-top-content` shipped `s8`, and §16 contradicted it by calling `s8` a locked preset property — the gaps are base values every preset inherits, so the "gap" entries left the Icon-cell and Squircle-cell locked-properties columns. |
| 3.4.1 | 2026-08-06 | §16 Squircle-cell geometry corrected from Figma `[SquircleCell] 3.4` — the table had S→`.squircle--m`, M→`.squircle--l`, L: TBD from the pre-rework Squircle scale. Now S/M/L → `.squircle--s/m/l` (40/48/56), start-slot fixed at the Squircle size, M locks vertical padding to `var(--sp-s8)`, two-row heights 60/64/76. shared.css slots realigned (m 40→48, l 48→56, m padding); page demos put `.squircle--s` in `squircle-s` cells (was `--m` bulging out of a 40 slot). Stale "Squircle CSS pending" TODO removed. |
| 3.4.0 | 2026-08-06 | Version realigned to the Figma component line (owner call): the Figma component is `[Cell] 3.4`, while the repo had been counting on its own up to 3.11.1. Per `spec-conventions` the spec version tracks Figma's, so `X` moved 11 → 4 and stays on 4 until the design moves. The contract itself did not change. Platform badges on the preview cover follow the same line (iOS / Android 3.4.0). |
| 3.11.1 | 2026-07-23 | §5: documented the existing `box` start-slot variant (`.cell-start-slot--box` — radius `s8`, `--surface-on-white` background, `--border-default` border, secondary icon colour); the modifier already shipped in shared.css but was absent from the spec. |
| 3.11.0 | 2026-05-26 | Breaking: `.cell-middle` → `.cell-start-text`, `.cell-end` → `.cell-end-slot`. Aligns CSS class names with Figma slot names. |
| 3.10.0 | 2026-05-26 | §2: Cell root restructured — all horizontal slots wrapped in `cell-top-content` container. `cell-description` slot added below top-content (hidden by default, boolean toggle via `.is-visible`). CSS: `.cell` → `flex-direction: column`; `.cell-top-content` takes padding and min-height. |
| 3.9.0 | 2026-05-26 | §16: added slot height constraints table (min/max-height) for Simple and Icon presets. Added `.cell--simple` modifier. Added §17 Card-wrap pattern. |
| 3.8.1 | 2026-05-25 | Overview: added CustomCell note — all presets are locked configurations of base CustomCell. Layout: added end-slot priority note. |
| 3.7.0 | 2026-04-30 | Added RTL section: default RTL=Off, chevron rtl:true, all other slots rtl:false, dir="rtl" on root reverses layout. |
| 3.6.0 | 2026-04-29 | Added mandatory prototype analytics and DS coverage contract. |
| 3.5.0 | 2026-04-28 | Added section 2 "Slot structure": all named Cell elements (start-slot, start-text, end-text, end-slot, description and their nested rows/sub-slots) are instances of slot.md with preset width/height/align properties. Structure tree with presets for each element. |
| 3.4.0 | 2026-04-28 | Updated spacing: padding s10 vertical / s16 horizontal (was s16/s8). Gap start-slot→start-text s4 (was s16). Start-slot size s40, placed elements aligned center vert/hor. |
| 3.3.0 | 2026-04-28 | Renamed `middle-slot` → `start-text`. Added `end-text` slot: hug width, higher layout priority than start-text (start-text truncates first), same row structure (top/middle/bottom), default text config `standard`, hidden by default. Updated gaps: start-text→end-text s4, end-text→end-slot s4. Updated anatomy diagram, Color Tokens, Typography tables. |
| 3.2.0 | 2026-04-27 | Added Description slot (3 configs: default / only text / only divider; 5 states). Added text config to all middle-slot rows: multiline (off/on) + standard/expanded mode with up to 4 surrounding slots per row. Added Skeleton section. Typography table extended with description-text row. |
| 3.1.0 | 2026-04-25 | Removed S/M/L sizes — single universal Cell. New padding: s16 vertical, s8 horizontal. Middle-slot restructured into top/middle/bottom rows, each with 2 before + 2 after hug slots. Gaps: start→middle s16, middle→end s4. Min-height s48. |
| 3.0.1 | 2026-04-23 | `rtl: false` by default for all IconContainers in slots. Only exception — end-slot `chevron` (`cell-chevron`, `rtl: true`). |
| 3.0.0 | 2026-04-23 | Initial version in Oymyakon DS 3. Slot rename: `leading` → `start-slot`, `trailing` → `end-slot`. Anatomy, states, motion (`pushHighlight`), color tokens, typography, spacing, a11y summary. |

---

# Cell · Accessibility Spec · Oymyakon DS 3

**Version:** 3.4.0 · **Status:** Draft · **Linked component:** [`cell.md`](https://super-dollop-pzmo65r.pages.github.io/cell.md)

---

## 1. Roles and Semantics

### Interactive Cell (default)

```html
<button
  class="cell"
  role="button"
  aria-label="Language, English"
  data-ds-component="Cell"
  data-ds-state="default">
  …
</button>
```

Use `<button>` for tappable cells. `aria-label` must include both title and subtitle when both are visible.

### Static (display-only) Cell

```html
<div class="cell" aria-label="ID check, Verified 2 days ago">
  …
</div>
```

No role needed — `<div>` is presentation. Add `aria-label` so screen readers announce it as a unit.

### Cell with chevron end-slot

Add a hint so screen readers announce the navigation intent:

```html
<button class="cell" aria-label="Payment method" aria-describedby="cell-hint-1">
  …
</button>
<span id="cell-hint-1" class="visually-hidden">Tap to open</span>
```

### Cell with Switch end-slot

The Switch is read as a **separate element** from the Cell. The Cell has its label; the Switch has its own `aria-label` and `role="switch"`:

```html
<div class="cell" aria-label="Notifications">
  …
  <button role="switch" aria-checked="true" aria-label="Notifications toggle">…</button>
</div>
```

### Disabled Cell

```html
<button class="cell is-disabled" disabled aria-label="Facial recognition, Not set up">
  …
</button>
```

Native `disabled` attribute suppresses pointer events and removes the element from tab order. Screen readers announce the disabled trait automatically.

### Selected Cell

```html
<button class="cell is-selected" aria-selected="true" aria-label="English">
  …
</button>
```

---

## 2. Keyboard Navigation

| Key | Behavior |
|---|---|
| `Tab` | Moves focus to the next interactive cell |
| `Shift+Tab` | Moves focus to the previous interactive cell |
| `Space` / `Enter` | Activates the cell (same as tap) |

Static cells (display-only) are not in the tab order.

---

## 3. Focus Indicator

Do not suppress `outline` on `.cell`. The browser's default focus outline is sufficient. Minimum tap/click target: `var(--sp-s48)` height (already enforced by `min-height`).

---

## 4. Contrast Requirements

| Element | Token | Requirement |
|---|---|---|
| Title text | `--text-and-icon-primary` on `--background-primary` | ≥ 4.5:1 (text) |
| Subtitle text | `--text-and-icon-secondary` on `--background-primary` | ≥ 4.5:1 (text) |
| Icon in start-slot | `--text-and-icon-primary` on `--background-primary` | ≥ 3:1 (UI component) |
| Chevron | `--text-and-icon-secondary` on `--background-primary` | ≥ 3:1 |
| Selected state | `--text-and-icon-primary` on `--pastel-drive-green1` | ≥ 4.5:1 (text) |

---

## 5. Platform Screen Reader Behavior

### Android · TalkBack

- Cell announced as: "{title}, {subtitle}" + trait `BUTTON` + hint "Double-tap to activate"
- Disabled: trait `DISABLED`, no hint
- Selected: trait `SELECTED`
- Chevron end-slot: no separate announcement; hint on cell "Double-tap to open"

### iOS · VoiceOver

- Cell announced as: "{title}, {subtitle}" + trait `.button` + hint "Double-tap to activate"
- Disabled: trait `.dimmed`, no activation hint
- Selected: trait `.selected`
- Switch end-slot: announced separately after the cell as "{label}, switch, on/off"

---

## 6. Reduced Motion

```css
@media (prefers-reduced-motion: reduce) {
  .cell { transition: none; }
}
```

State changes (pressed background, disabled opacity) happen instantly. No animations under reduced-motion.

---

## Changelog

| Version | Date | Change |
|---|---|---|
| 3.4.0 | 2026-08-06 | Version realigned to the component line `[Cell] 3.4` (owner call). The file had been on its own `1.0.0` counter, unlike every other a11y spec, which tracks its component (Button 3.3.0, Floating Button 3.3.0, Rating 3.2.3). No content change in this row. |
| 1.0.0 | 2026-05-19 | Initial a11y spec — roles, keyboard, contrast, TalkBack/VoiceOver, reduced motion |

---

## Machine contract — `specs/components/cell/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": "cell",
  "name": "Cell",
  "version": "3.4.7",
  "description": "List row — a universal clickable element for displaying structured content in vertical lists. All presets are locked configurations of the base CustomCell.",
  "files": {
    "spec": "specs/components/cell/cell.md",
    "a11y": "specs/components/cell/cell-a11y.md",
    "preview": "src/cell.njk",
    "css": "src/shared/shared.css"
  },
  "root": {
    "class": "cell",
    "dataDsComponent": "cell"
  },
  "anatomy": {
    "topContent": {
      "class": "cell-top-content",
      "notes": "Flex row wrapping all horizontal slots; carries the padding (s10 vertical / s16 horizontal) and min-height s48."
    },
    "startSlot": {
      "class": "cell-start-slot",
      "optional": true,
      "notes": "Left slot — IconContainer by default (s40); hidden variant available. .cell-start-slot--box for boxed (squircle/icon-bg) presets."
    },
    "startText": {
      "class": "cell-start-text",
      "notes": "Main text area — vertical stack of up to 3 rows. Fills remaining width; truncates first when end-text is present."
    },
    "titleRow": {
      "class": "cell-title",
      "notes": "top-row — always visible; the ROW itself is a TextRow instance (.text-row), .cell-title is its text element (.text-row__text.cell-title). Main Body / --text-and-icon-primary by default."
    },
    "subtitleRow": {
      "class": "cell-subtitle",
      "optional": true,
      "notes": "middle-row — hidden by default; a TextRow instance (.text-row), .cell-subtitle is its text element. Compact Body / --text-and-icon-secondary."
    },
    "additionalRow": {
      "class": "cell-additional",
      "optional": true,
      "notes": "bottom-row — hidden by default; a TextRow instance (.text-row), .cell-additional is its text element. Compact Body / --text-and-icon-secondary."
    },
    "endText": {
      "class": "cell-end-text",
      "optional": true,
      "notes": "Secondary text area — hug width, never truncates (higher layout priority than start-text). Same 3-row structure. Hidden by default."
    },
    "endSlot": {
      "class": "cell-end-slot",
      "optional": true,
      "notes": "Right slot — icon / badge / value / switch / chevron. One or two simultaneously (end-slot-2 hidden by default, gap s4)."
    },
    "description": {
      "class": "cell-description",
      "optional": true,
      "notes": "Area below the main body. ALWAYS present in the DOM; shown by adding .is-visible (display:none by default)."
    },
    "descriptionDivider": {
      "class": "cell-description__divider",
      "optional": true,
      "notes": "Divider — --border-default. With start-slot: left offset aligns to text; without: full-bleed."
    },
    "descriptionText": {
      "class": "cell-description__text",
      "optional": true,
      "notes": "Description text — Compact Body / --text-and-icon-secondary by default."
    }
  },
  "axes": {
    "preset": {
      "title": "Preset",
      "type": "enum",
      "values": [
        "custom",
        "simple",
        "icon",
        "squircle-s",
        "squircle-m",
        "squircle-l"
      ],
      "default": "custom",
      "css": {
        "mechanism": "custom = bare .cell (all slots configurable); presets = .cell--simple / .cell--icon / .cell--squircle-s|m|l modifiers + matching data-ds-preset"
      },
      "figma": {
        "kind": "none",
        "notes": "Preset components exist in the Figma library; stable keys not captured yet."
      },
      "constraints": [
        "Predefined presets are LOCKED contracts — slot contents, gaps, colours and structure cannot be modified inside them; for freedom use custom.",
        "icon preset: start gap locked to var(--sp-s8), plain 24×24 icon in --text-and-icon-primary.",
        "squircle presets: start-slot size tracks the Squircle size exactly (s: 40×40, m: 48×48, l: 56×56) — verified against Figma [SquircleCell] 3.4.",
        "squircle-m locks the top-content vertical padding to var(--sp-s8) (S/L keep var(--sp-s10)); two-row heights land on 60/64/76.",
        "EndSlot in all predefined presets: chevron in --text-and-icon-primary, locked."
      ]
    },
    "startSlot": {
      "title": "Start slot",
      "type": "enum",
      "values": [
        "icon",
        "box",
        "hidden"
      ],
      "default": "icon",
      "css": {
        "mechanism": "render/omit .cell-start-slot (IconContainer inside, slot size s40); box → add .cell-start-slot--box (radius s8, --surface-on-white bg, --border-default border, secondary icon colour); hidden → start-text aligns to the left padding"
      },
      "figma": {
        "kind": "variant-property",
        "property": "Start-slot",
        "notes": "Property name per spec slot vocabulary; verify against the Figma set when keys are captured."
      }
    },
    "startTextRows": {
      "title": "Start-text rows",
      "type": "enum",
      "values": [
        "title",
        "title-subtitle",
        "title-subtitle-additional"
      ],
      "default": "title",
      "css": {
        "mechanism": "rows inside .cell-start-text: .cell-title (always) + .cell-subtitle + .cell-additional"
      },
      "figma": {
        "kind": "variant-property",
        "property": "Rows"
      }
    },
    "endText": {
      "title": "End-text",
      "type": "boolean",
      "default": false,
      "css": {
        "mechanism": "render .cell-end-text (same 3-row structure as start-text; default text config standard; web CSS right-aligns its text)"
      },
      "figma": {
        "kind": "variant-property",
        "property": "End-text"
      },
      "constraints": [
        "End-text never truncates — when both are visible, start-text truncates first."
      ]
    },
    "endSlot": {
      "title": "End slot",
      "type": "enum",
      "values": [
        "none",
        "chevron",
        "value",
        "badge",
        "switch",
        "icon"
      ],
      "default": "none",
      "css": {
        "mechanism": ".cell-end-slot container; value → .cell-value; chevron/icon → IconContainer; badge/switch → nested DS components with their own default classes"
      },
      "figma": {
        "kind": "layout",
        "note": "Layer 🔵EndSlot inside topContainer, toggled by visibility — [CustomCell] 3.3 is a plain component (no variants) and the preset sets [SimpleCell] / [IconCell] / [SquircleCell] 3.4 expose only size / State / RTL."
      },
      "constraints": [
        "chevron is the ONLY end-slot icon with rtl:true (mirrors in RTL); utility icons keep rtl:false.",
        "A Switch in end-slot is its own DS component: it owns data-ds-action=\"toggle\" and its own disabled appearance."
      ]
    },
    "endSlotCount": {
      "title": "End-slot count",
      "type": "enum",
      "values": [
        "1",
        "2"
      ],
      "default": "1",
      "css": {
        "mechanism": "one .cell-end-slot container; it holds one or two items, var(--sp-s8) apart, hugging as a group at the right edge"
      },
      "figma": {
        "kind": "layout",
        "note": "Slot count inside the single 🔵EndSlot layer. KNOWN GAP: the masters read on 2026-08-06 ([CustomCell] 3.3, [SimpleCell] / [IconCell] / [SquircleCell] 3.4) offer no such choice yet — the design catches up at the next update."
      },
      "notes": "The count is a variation of the end-slot itself, not a separate slot: two items share one container. Any combination of end-slot variants is valid; common pairings become presets once they settle (spec §8)."
    },
    "description": {
      "title": "Description slot",
      "type": "enum",
      "values": [
        "hidden",
        "default",
        "only-text",
        "only-divider"
      ],
      "default": "hidden",
      "css": {
        "mechanism": ".cell-description is always in the DOM; show with .is-visible; compose .cell-description__divider + .cell-description__text per config"
      },
      "figma": {
        "kind": "variant-property",
        "property": "Description"
      }
    },
    "descriptionState": {
      "title": "Description state",
      "type": "enum",
      "values": [
        "default",
        "error",
        "success",
        "warning",
        "inform",
        "disabled"
      ],
      "default": "default",
      "css": {
        "mechanism": "NO dedicated modifier classes exist — apply the semantic colour token as an instance-level override on .cell-description__text (error / success / warning / accent-inform / --text-and-icon-disabled)"
      },
      "figma": {
        "kind": "variant-property",
        "property": "State",
        "notes": "State of the description text, not of the whole Cell."
      },
      "constraints": [
        "Spec-defined (§9); only default and disabled are exercised in the living code — error/success/warning/inform render via inline token override, there is no ready-made class."
      ]
    },
    "textMultiline": {
      "title": "Text multiline (per row)",
      "type": "boolean",
      "default": false,
      "css": {
        "mechanism": "per-row text config (spec §6/§7): off = single line with ellipsis; on = wraps"
      },
      "figma": {
        "kind": "variant-property",
        "property": "Multiline"
      },
      "constraints": [
        "multiline:on hides that row's slot-end-1 / slot-end-2."
      ]
    },
    "textMode": {
      "title": "Text config mode (per row)",
      "type": "enum",
      "values": [
        "standard",
        "expanded"
      ],
      "default": "standard",
      "css": {
        "mechanism": "per-row text config (spec §6/§7): expanded adds up to 4 hug slots around the text (slot-start-1/2, slot-end-1/2), IconContainer inside by default"
      },
      "figma": {
        "kind": "variant-property",
        "property": "Config"
      }
    },
    "rtl": {
      "title": "RTL",
      "type": "boolean",
      "default": false,
      "css": {
        "mechanism": "dir=\"rtl\" on the root — the flex row reverses automatically, no extra CSS"
      },
      "figma": {
        "kind": "variant-property",
        "property": "RTL"
      },
      "constraints": [
        "Only the chevron mirrors (rtl:true); start-slot and utility end-slot icons do not flip."
      ]
    }
  },
  "states": {
    "root": [
      "standard",
      "pressed",
      "disabled",
      "selected",
      "skeleton"
    ]
  },
  "constraints": [
    "Full width of the container; height hugs content with min-height var(--sp-s48).",
    "Padding var(--sp-s10) vertical / var(--sp-s16) horizontal; inter-slot gaps var(--sp-s4) (icon/squircle presets lock the start gap to var(--sp-s8)).",
    "Pressed feedback is background-color only (pushHighlight, --component-push-highlight) — a Cell never scales on press.",
    "Disabled = .is-disabled on the root (opacity 40%, no interaction, no animation); Selected = .is-selected.",
    "Never adapt by overriding component CSS — card presentation goes through the .cell-card-wrap wrapper (radius + background + overflow:hidden live on the wrapper).",
    "All values via DS tokens; raw px/em/rem/hex prohibited."
  ],
  "analytics": {
    "dataDsComponent": "cell",
    "actions": [
      "tap",
      "open",
      "select"
    ],
    "targets": [
      "end-slot"
    ],
    "valueAttr": "data-ds-value",
    "notes": "Root is the tap target by default; end-slot is a separate target only when it is an independent action and not a nested DS component. A Switch owns its own toggle event. Disabled cells keep coverage, emit no taps. Spec §19 prose shows data-ds-component=\"Cell\" — the living pages use kebab-case \"cell\" (component-authoring rule)."
  },
  "rtl": {
    "supported": true,
    "notes": "Default Off. dir=\"rtl\" on the root reverses the slot order; text alignment follows dir automatically."
  }
}
```
