# List row

> Settings and navigation cells with a token-colored icon tile, title, subtitle and a trailing value, switch, badge, check or chevron, grouped in inset sections with headers and footers. On touch down the highlight floods in, the tile sinks and the chevron leans forward; on release the highlight lingers and fades.

- Type: Component (data)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/list-row
- Markdown: https://goodui.dev/native/components/list-row.md
- Registry item: https://goodui.dev/native/r/list-row.json
- Dependencies: react-native-reanimated, react-native-svg
- Keywords: react native settings list, list item, table view cell, grouped list, inset grouped, settings row, menu row with chevron, expo list row

## When to use

- Settings screens: account, notifications, privacy, each row leading to a detail screen or holding a switch.
- Profile and account menus with a value on the right: plan, language, payment method.
- Short pickers where one row is checked: language, currency, appearance.
- A destructive row at the end of a group: sign out, delete account.

## When not to use

- Rows that reveal content in place: use accordion.
- Rows with swipe gestures: wrap the row content in swipe-actions.
- Long, sectioned, scrolling data such as contacts: use sticky-header-list.
- Rows the person sorts by dragging: use reorderable-list.
- Two to five mutually exclusive views: use segmented-control.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/list-row.json
npx expo install --fix
```

The component lands in `components/goodui-native/list-row/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs badge, locale, switch.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { ListRow, ListSection } from "@/components/goodui-native/list-row/list-row";
import { router } from "expo-router";
import { useState } from "react";
import Svg, { Path } from "react-native-svg";

const bell = (color: string, size: number) => (
  <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
    <Path d="M6 16v-5a6 6 0 0 1 12 0v5l1.5 2h-15ZM10 20.5a2 2 0 0 0 4 0" stroke={color} strokeWidth={2} strokeLinejoin="round" />
  </Svg>
);

export function SettingsScreen() {
  const [push, setPush] = useState(true);
  return (
    <ListSection header="Notifications" footer="We only send booking reminders and receipts.">
      <ListRow title="Push notifications" icon={bell} iconTone="accent" switchValue={push} onSwitchChange={setPush} />
      <ListRow title="Reminder time" value="1 hour before" onPress={() => router.push("/settings/reminders")} />
    </ListSection>
  );
}
```

## Examples

### A picker with a drawn check

```tsx
<ListSection header="Currency">
  {(["EUR", "USD", "GBP"] as const).map((c) => (
    <ListRow key={c} title={c} checked={currency === c} onPress={() => setCurrency(c)} />
  ))}
</ListSection>
```

### Async row and destructive row

```tsx
<ListSection>
  <ListRow title="Restore purchases" onPress={() => api.restore()} chevron={false} />
  <ListRow title="Sign out" destructive onPress={signOut} />
</ListSection>
```

### Badge and soft tile

```tsx
<ListRow title="Messages" icon={chat} iconTone="accent" iconVariant="soft" badge={3} onPress={openMessages} />
```

## API reference

### ListRow

`import { ListRow } from "@/components/goodui-native/list-row/list-row";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` (required) | `string` | – | Row title. Sentence case; wraps to two lines before it truncates. |
| `subtitle` | `string` | – | Second line under the title. |
| `icon` | `ListRowIcon` | – | Icon in a colored tile at the leading edge. A function gets the tile's foreground color and icon size. |
| `iconTone` | `"success" \| "warning" \| "danger" \| "accent" \| "neutral"` | – | Tile color. `neutral` gray by default; the accent or a status only when it means something. |
| `iconVariant` | `"solid" \| "soft"` | `"solid"` | `solid` tile with an on-color icon (iOS Settings), or `soft` tinted tile with a colored icon. |
| `value` | `string` | – | Trailing value text, for example "Wi-Fi name", "English" or "€12.00". |
| `badge` | `number \| string` | – | Trailing badge: a count (unread, updates) or a short status label such as "New". |
| `chevron` | `boolean` | – | Trailing chevron. Default: shown when the row has `onPress` and is not destructive, checkable or a switch. |
| `checked` | `boolean` | – | Makes the row a radio choice with a check that draws in when selected. Use for pickers like language. |
| `switchValue` | `boolean` | – | Puts a Switch in the trailing slot (controlled). The switch carries the title as its label. |
| `defaultSwitchValue` | `boolean` | – | Switch start state when uncontrolled. |
| `onSwitchChange` | `(value: boolean) => unknown` | – | Called with the new switch state. Return a promise for an optimistic switch with rollback. |
| `trailing` | `ReactNode` | – | Any trailing element instead of value, badge and chevron. Keep it non-interactive unless the row has no `onPress`. |
| `onPress` | `() => unknown` | – | Tap on the row. Return a promise and the trailing slot shows a spinner until it settles; a rejection shows an error mark with an error haptic. |
| `onLongPress` | `() => void` | – | Long press on the row, for example to copy the value. |
| `destructive` | `boolean` | `false` | Red title (and danger tile) for an action that removes or signs out. No chevron. |
| `disabled` | `boolean` | `false` | Dims the control and ignores presses. |
| `size` | `"sm" \| "md"` | `"md"` | `sm` 44 pt rows with 28 pt tiles, `md` 52 pt rows with 32 pt tiles. |
| `haptic` | `HapticName \| false` | – | Haptic on press. Default: `select` for checkable rows, none for navigation rows. |
| `accessibilityLabel` | `string` | – | Screen reader label on native. Defaults to title, subtitle, value and badge. The web reads the visible text: a pressable row there is a button named by its text, so a custom label applies to static rows only. |
| `accessibilityHint` | `string` | – | Describes what happens on activation, read after the label. |
| `labels` | `Partial<ListRowLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

### ListSection

`import { ListSection } from "@/components/goodui-native/list-row/list-row";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` | – | Rows. Each ListRow inside draws its own hairline, inset to line up with the title. |
| `header` | `string` | – | Short heading above the group, sentence case. |
| `footer` | `ReactNode` | – | Note under the group: a string, or a node such as a text with a link. |
| `variant` | `"inset" \| "plain"` | `"inset"` | `inset` is a rounded card inside the screen gutter (iOS inset grouped); `plain` runs edge to edge. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- A pressable row is a button; with `checked` it is a radio with checked in accessibilityState and aria-checked on the web. Disabled and busy (while an async onPress runs) are set in both.
- On native the row reads one label built from title, subtitle, value and badge. On the web the visible text names it, so the name always starts with the title; a custom accessibilityLabel applies there to static rows only, never to a pressable one.
- A switch row is not a button: the Switch inside is the control, labelled with the title (and subtitle). The row's text stays readable.
- Section headers are headers. Rows are at least 44 pt tall.
- A failed async press is announced ('<title> failed') and shown as an error mark.

## Motion

- Touch down: the highlight fades in within 80 ms, the icon tile scales to 0.9 and the chevron moves 3 pt forward, both on the snappy spring.
- Release: the highlight fades out over 320 ms so a quick tap still reads; tile and chevron spring back.
- Checking a row draws the check in; the first render shows it complete.
- Reduced motion: no tile scale or chevron travel, the check appears without drawing; the highlight still fades.

## Haptics

- select on press for checkable rows by default, none for navigation rows (set haptic to change it)
- error when an async onPress rejects
- the Switch in a switch row plays its own toggle-on or toggle-off cue

## Performance

- Press feedback runs on the UI thread with shared values; React state only changes for async status.
- Rows are plain views: for hundreds of rows render them in a FlatList or use sticky-header-list.

## Notes for AI

- Always put rows inside ListSection when they form a group; it draws the surface, rounded corners and the inset hairlines. A ListRow alone has no background.
- Pass icons as functions `(color, size) => <Icon />` so the tile picks the right color for solid and soft variants.
- A switch row does not take onPress; use switchValue + onSwitchChange (return a promise for a server setting).
- chevron defaults on for pressable rows; pass chevron={false} for actions that do not navigate.
- Do not nest Pressables in `trailing` of a pressable row.
- Tile tones: neutral or accent for most rows; success, warning and danger only when the row means that status (danger for destructive rows).

## Related

- [Switch](https://goodui.dev/native/components/switch.md): An iOS-style switch with squash and stretch: the thumb stretches toward where it is going on touch down, then travels on a spring while the track color follows. Settings-row layout, tones, sizes and optimistic async changes.
- [Badge](https://goodui.dev/native/components/badge.md): A count, dot or status badge that springs in when it appears, pops when its value changes, caps at 99+ and shrinks away at zero; BadgeAnchor pins it to an icon or avatar.
- [Accordion](https://goodui.dev/native/components/accordion.md): Collapsible rows whose panels open from their measured height on the UI thread, with content that drops into place and a chevron that turns on the same spring.
- [Swipe actions](https://goodui.dev/native/components/swipe-actions.md): Row actions that unfold with the finger; a long swipe lets the edge action take over and run, and destructive actions collapse the row before it leaves.
- [Sticky header list](https://goodui.dev/native/components/sticky-header-list.md): A sectioned list whose sticky headers change as they stick: the title shrinks toward the leading edge and a surface with a hairline (frosted on the web) fades in under it. A contacts-style index on the side jumps between sections while a large letter follows the finger, with a tick per letter.

## Guidance for AI tools

List row: Settings and navigation cells with a token-colored icon tile, title, subtitle and a trailing value, switch, badge, check or chevron, grouped in inset sections with headers and footers. On touch down the highlight floods in, the tile sinks and the chevron leans forward; on release the highlight lingers and fades. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
