v3.4.7
Cell

The foundational list row — a universal tappable element for settings, contacts, menus, and transactions.

3.4.0 3.4.0 -- 3.4.7

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
1
2
3
4
5
6

States

Every preset has 3 states: Standard, Disabled, Skeleton

Standard
Disabled
Skeleton

Standard

1
2
3
2
3
4
5
3
6
  • 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
2
3
2
3
4
5
3
6
  • 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

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.

1
2
3
4
5
  • 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

S — Squircle 40 · row 60
M — Squircle 48 · row 64
L — Squircle 56 · row 76
  • 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

Title only
Title + chevron
Title + value
Title + subtitle
start-slot + title + subtitle + chevron
start-text + end-text
  • 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
Animation
Cell uses the pushHighlight pattern — background color change on press only, no scale. Scaling a list row would deform adjacent cells and is inconsistent with platform conventions. Source: tokens/rules/motion-rules.md §6.1.
EventPatternTokenDurationCurveCSS
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);
}
Reduced motion: @media (prefers-reduced-motion: reduce) sets all motion tokens to 0ms linear. State changes happen instantly — no background color fade, no opacity fade.
PlatformPress implementation
Webbackground-color transition via --component-push-highlight
AndroidMaterial ripple over cell background (native)
iOSUITableViewCell highlight — backgroundColor change
FlutterInkWell with custom splashColor from Surface/PressedOverlay
Usage
When to use
Settings lists — profile, app preferences, payment methods.
Contacts, drivers, passengers — any person-list row.
Trip history, transactions — structured data rows with title + value.
Menus — categories, order options, order summary items.
Address books — any homogeneous list of navigable items.
When not to use
When a cover-image card is needed — use Card instead.
When multi-line rich text is required — use a dedicated content component.
When multiple independent actions are needed per row — consider SwipeCell or a context menu.
As a standalone element outside of a list — Cell is a list building block, not a standalone card.
Related componentRelation
ListWrapper that groups Cells with shared border-radius and dividers.
IconContainerUsed in start-slot and end-slot.
AvatarCan replace IconContainer in start-slot for person rows.
BadgeNumeric count or dot indicator in end-slot.
SwitchInline toggle in end-slot — its own DS component and tap target.
DividerBottom border of the cell body; also used inside the description slot.
Bottom SheetCells used as menu items inside Bottom Sheet.
Accessibility
Interactive cells must use <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.
VariantRoleKey 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>
KeyBehavior
Tab / Shift+TabMoves focus between interactive cells.
Space / EnterActivates the cell.
ElementTokenRequirement
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)