The foundational list row — a universal tappable element for settings, contacts, menus, and transactions.
Overview
Cell is the fundamental building block for lists — a universal tappable row for settings, menu items, transactions, addresses, and contacts. It always fills its container width, and every preset is a locked configuration of a single CustomCell base with all slots configurable.
-
1
Simple
-
2
Icon
-
3
Squircle · S
-
4
Squircle · M
-
5
Squircle · L
-
6
Custom
States
Every preset has 3 states: Standard, Disabled, Skeleton
Standard
-
1
Background No background by default — any DS color style can be applied
-
2
Title var(--text-and-icon-primary) by default — the start-text and end-text title rows
-
3
Subtitle var(--text-and-icon-secondary) by default — subtitle, additional and description rows
-
4
Chevron var(--text-and-icon-primary) by default
-
5
Icon var(--text-and-icon-primary) by default
-
6
Squircle All styles are inherited from the Squircle component
Disabled
-
1
Background Same as Standard
-
2
Title var(--text-and-icon-disabled) — set once by the root state class, children inherit
-
3
Subtitle var(--text-and-icon-disabled)
-
4
Chevron var(--text-and-icon-disabled)
-
5
Icon var(--text-and-icon-disabled)
-
6
Squircle Its own disabled style from the Squircle component
Skeleton
-
1
General Placeholder geometry and the shimmer motion come from the Skeleton component
Anatomy
Cell is composed of two layers: the top-content row (horizontal slots) and an optional description below. Every slot is independent — only start-text is required.
- Top content
-
1
start-slot Optional. Any element: icon, squircle, image, avatar. Hug size.
-
2
start-text Required. Fills remaining space. Up to 3 rows: title, subtitle, additional. Truncates with ellipsis.
-
3
end-text Optional. Hug width. Right-aligned. Never truncated.
-
4
end-slot Optional. Chevron / value / badge / switch / icon. Up to 2 simultaneously.
- Bottom
-
5
description Optional. Below top-content. Divider + text. Hidden by default, shown with .is-visible.
Layout
The specimen below is CustomCell — the base component with every slot turned on. Every predefined preset (Simple, Icon, Squircle S / M / L) is this same cell with slots locked into a fixed configuration, so the box and the gaps described here hold for all of them. Cell always fills its container width, its height hugs the content down to a minimum, and the margins are asymmetric — each text block stands var(--sp-s8) from its neighbour and the end-slot keeps the same var(--sp-s8).
- Base
-
1
CustomCell All slots configurable: start-slot, the three start-text rows, end-text, up to two end-slots and the description. The presets lock a subset of them — none of them adds anything the base does not have
- Box
-
2
Width Always fills the container — Cell is never sized by its content
-
3
Height Hug, with min-height: var(--sp-s48) on the top-content row
-
4
Horizontal padding var(--sp-s16) left and right
-
5
Vertical padding var(--sp-s10) top and bottom — the M Squircle row is the one exception at var(--sp-s8)
- Gaps
-
6
start-slot → start-text var(--sp-s8)
-
7
start-text → end-text var(--sp-s8)
-
8
end-text → end-slot var(--sp-s8) — and the same between two elements stacked inside one end-slot
- Priority
-
9
End side wins The end-slot keeps its width and start-text truncates first. The priority can be moved to the start side when a layout needs it
Squircle-cell comes in three sizes whose names map 1:1 onto the Squircle component's own scale. The start-slot is fixed at exactly the Squircle size and adds no padding of its own, so the row height follows the Squircle.
Squircle sizes
-
1
S .squircle--s 40 × 40 in a var(--sp-s40) slot · two-row height 60
-
2
M .squircle--m 48 × 48 in a var(--sp-s48) slot · two-row height 64 · vertical padding var(--sp-s8)
-
3
L .squircle--l 56 × 56 in a var(--sp-s56) slot · two-row height 76
-
4
Chevron stays 16 The end-slot icon does not scale with the preset size
Slot combinations
-
1
Only start-text is required Every other slot is optional and any combination of them is valid
-
2
Two end-slots maximum A value plus a chevron, or a badge plus a chevron — never more
Card-wrap pattern
-
1
A wrapper, not a variant Any preset can sit inside a rounded wrapper to read as a standalone button; the wrapper owns the radius, background and overflow: hidden so the press animation clips to it, and Cell keeps its own var(--sp-s16) padding
tokens/rules/motion-rules.md §6.1.| Event | Pattern | Token | Duration | Curve | CSS |
|---|---|---|---|---|---|
| Press / Release | pushHighlight | --component-push-highlight | 150ms | linear | transition: background-color 150ms cubic-bezier(0.25, 0.25, 0.75, 0.75) |
| Default → Disabled | Transforming/State/Default | --transforming-state-default | 200ms | slow-ease-out | transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) |
| Default → Selected | Transforming/State/Default | --transforming-state-default | 200ms | slow-ease-out | transition: background-color 200ms cubic-bezier(0.4, 0, 0.2, 1) |
.cell {
transition: background-color var(--component-push-highlight);
/* --component-push-highlight = 150ms cubic-bezier(0.25, 0.25, 0.75, 0.75) */
}
.cell:active { background-color: var(--surface-on-grey); }
.cell.is-disabled {
opacity: 0.4;
pointer-events: none;
transition: opacity var(--transforming-state-default);
}
.cell.is-selected {
background-color: var(--pastel-drive-green1);
transition: background-color var(--transforming-state-default);
}
@media (prefers-reduced-motion: reduce) sets all motion tokens to 0ms linear. State changes happen instantly — no background color fade, no opacity fade.| Platform | Press implementation |
|---|---|
| Web | background-color transition via --component-push-highlight |
| Android | Material ripple over cell background (native) |
| iOS | UITableViewCell highlight — backgroundColor change |
| Flutter | InkWell with custom splashColor from Surface/PressedOverlay |
| Related component | Relation |
|---|---|
| List | Wrapper that groups Cells with shared border-radius and dividers. |
| IconContainer | Used in start-slot and end-slot. |
| Avatar | Can replace IconContainer in start-slot for person rows. |
| Badge | Numeric count or dot indicator in end-slot. |
| Switch | Inline toggle in end-slot — its own DS component and tap target. |
| Divider | Bottom border of the cell body; also used inside the description slot. |
| Bottom Sheet | Cells used as menu items inside Bottom Sheet. |
<button>. The aria-label must combine title and subtitle so screen readers announce both. Disabled cells use the native disabled attribute — it suppresses pointer events and removes the element from tab order automatically.| Variant | Role | Key aria attributes |
|---|---|---|
| Interactive | <button> | aria-label="Title, Subtitle" |
| With chevron | <button> | aria-describedby pointing to visually-hidden hint "Tap to open" |
| Disabled | <button disabled> | Native disabled — removed from tab order, trait announced automatically |
| Selected | <button> | aria-selected="true" |
| With Switch | Cell + Switch | Cell: aria-label for the row. Switch: separate role="switch" + aria-checked + own label. |
<!-- Interactive cell with chevron --> <button class="cell" data-ds-component="cell" aria-label="Language, English" aria-describedby="hint-1"> <div class="cell-start-text">…</div> <div class="cell-end-slot"><!-- chevron svg --></div> </button> <span id="hint-1" class="visually-hidden">Tap to open</span> <!-- Disabled cell --> <button class="cell is-disabled" disabled aria-label="Facial recognition, Not set up"> … </button> <!-- Selected cell --> <button class="cell is-selected" aria-selected="true" aria-label="Cash"> … </button>
| Key | Behavior |
|---|---|
| Tab / Shift+Tab | Moves focus between interactive cells. |
| Space / Enter | Activates the cell. |
| Element | Token | Requirement |
|---|---|---|
| Title | --text-and-icon-primary on --background-primary | ≥ 4.5:1 (text) |
| Subtitle | --text-and-icon-secondary on --background-primary | ≥ 4.5:1 (text) |
| Title on Selected bg | --text-and-icon-primary on --pastel-drive-green1 | ≥ 4.5:1 (text) |