<!-- https://getkonjo.com/design/components/row · source: docs/design/components/row.md -->

# Row

The generic list row: an optional leading slot, a title, an optional subtitle, a trailing slot,
and a chevron when it goes somewhere.

```kata-specimen
row-variants
```

> Source: [`Row.tsx`](../../../src/components/ui/Row.tsx). How lists are built from it:
> [index-list](../patterns/index-list.md).

## When to use it

- Any list of things you open: techniques, students, classes, settings.
- A static fact with a label and a value, with `showChevron={false}`.

Use `FeedRow` for the Social feed and `SessionRow` for training history — they are this row with
feature-specific content. Do not build a third.

## Anatomy

| Part | Token |
|---|---|
| Row floor | `controlHeight.row` 64 — a floor, never a fixed height |
| Vertical padding | `spacing.s12`, on the content column |
| Gaps | `spacing.s12` leading to text and text to trailing; `spacing.s4` title to subtitle |
| Title | `typography.rowTitle` 16/600, `text.primary` |
| Subtitle | `typography.caption` 13/400, `text.secondary` |
| Chevron | `caretRight` at `iconSize.inline` 16, `text.secondary` |
| Divider | 1px `border.hairline` on `surface.card`, inset to the text edge, dropped on the last row (`isLast`) |

The hairline belongs to the content column, so it starts where the words start and never runs
under the avatar. On the page, five rows or fewer get no hairlines at all — 12pt between groups.

## States

```kata-specimen
row-states
```

- **Pressed**: `opacity.pressed` 0.85 across the row.
- **Focus**: a 2px `border.focus` ring, inset.
- **Static**: no press, no chevron, still one grouped element for a screen reader.
- **Disabled**: none. A row that cannot open says why in its subtitle.
- **Loading**: skeleton rows of the same shape, in the list that owns them.

**On web the ring is the component's own.** `Row` draws the 2px `border.focus` ring itself
(just inside the row, because the card a row sits in clips anything drawn outside it), as an outline so focusing it moves nothing. It shows for keyboard focus only — a
mouse or touch press does not light it — and native, which has no keyboard focus, is unchanged.

## Content

- The subject is first and boldest. Metadata goes in the subtitle, never above the title.
- Neither line truncates. A long name wraps and the row grows.
- A count in the trailing slot is `caption` in `text.secondary`; a status is a [Badge](badge.md).

## Accessibility

- A pressable row is role `button` by default and reads title and subtitle together.
- A static row groups its lines by hand (`accessible` plus a label), so it is read once, not as
  three fragments.
- **`trailingInteractive`** splits the row when the trailing slot holds something tappable — a
  ⋯ or a switch. On iOS a pressable fuses its children into one element, so a nested button is
  unreachable by VoiceOver. With the flag the text column becomes the pressable and the trailing
  control stays its own stop.
- The subtitle is `text.secondary`, not `text.tertiary`, because a row sits directly on
  `surface.page` as often as on a card, and `text.tertiary` on `surface.page` is 4.23:1, under
  the 4.5:1 body text needs.

## Do and don't

```kata-specimen
row-hierarchy
```

## Code

```tsx
import { Avatar, Card, Row, Text } from '../../components/ui';

<Card padded={false}>
  <Row
    title="Ana Torres"
    subtitle="Green · 14 months"
    leading={<Avatar name="Ana Torres" size={32} />}
    onPress={() => openStudent(ana.id)}
    testID="roster-row-ana"
  />
  <Row title="Hand techniques" trailing={<Text variant="caption" tone="secondary">16</Text>} onPress={openHand} isLast />
</Card>
```
