# List

> A React list for settings, contacts, inboxes and orders: iOS grouped panels, selection, row menus, swipe actions and drag to reorder.

- Type: React component (Tables & Lists)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.1.0
- Install: `npx wingo-ui@latest add list`
- Docs: https://wingo-ui.com/components/list

## Interaction

Tap or click a row to open it; navigation rows end in a chevron and tint on hover and press. On a phone, swipe a row left to uncover its trailing actions (Archive, Delete) or right for the leading ones (Mark read): the icons grow in as they come out, a short swipe snaps open, and swiping past 60% runs the outermost action with a haptic tick while a delete slides the row away and the list closes the gap. Only one row stays open; tapping it, tapping elsewhere or scrolling closes it. Long press a row on touch for a bottom sheet with the same actions; on desktop the ⋯ button (shown on hover or focus, always on touch) opens them as a menu, right click does too, and swipeWithMouse lets a mouse drag swipe. Selectable lists are one Tab stop: Up / Down, Home / End and PageUp / PageDown move, Space toggles, Shift + Space, Shift + arrows and Shift + click select a range, Ctrl / Cmd + A selects all, Escape clears, Enter opens. Shift + F10 or the Menu key opens the row menu and focus comes back to the row when it closes; Delete runs an action marked with the delete shortcut. Sortable lists show a grip: drag it (a long press on touch, so the page still scrolls), or focus it and press Space to lift, arrows to move, Space to drop, Escape to cancel; Alt + Up / Down on a focused row or grip moves it one step. Rows enter and leave on a soft spring; reduced motion turns movement into fades and snaps.

## Installation

```bash
npx wingo-ui@latest add list
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/list` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/list.tsx`

npm dependencies: `npm i @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities @radix-ui/react-slot lucide-react motion`

Wingo UI dependencies (installed automatically):

- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Checkbox](https://wingo-ui.com/components/checkbox) ([Markdown](https://wingo-ui.com/components/checkbox.md))
- [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu) ([Markdown](https://wingo-ui.com/components/dropdown-menu.md))
- [Empty State](https://wingo-ui.com/components/empty-state) ([Markdown](https://wingo-ui.com/components/empty-state.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Progress](https://wingo-ui.com/components/progress) ([Markdown](https://wingo-ui.com/components/progress.md))
- [Skeleton](https://wingo-ui.com/components/skeleton) ([Markdown](https://wingo-ui.com/components/skeleton.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [useLongPress](https://wingo-ui.com/components/use-long-press) ([Markdown](https://wingo-ui.com/components/use-long-press.md))
- [useSelection](https://wingo-ui.com/components/use-selection) ([Markdown](https://wingo-ui.com/components/use-selection.md))

## Usage

```tsx
import { Archive, Bell, Globe, Trash2, User } from "lucide-react"
import { List, type ListGroupData } from "@/components/ui/list"
import { Switch } from "@/components/ui/switch"

export function Settings({ onDelete }: { onDelete: (id: string) => void }) {
  const groups: ListGroupData[] = [
    {
      id: "account",
      label: "Account",
      items: [
        { id: "profile", title: "Profile", description: "emma.carter@northwind.app", icon: <User />, href: "/settings/profile" },
        { id: "notifications", title: "Notifications", icon: <Bell />, trailing: <Switch defaultChecked aria-label="Notifications" /> },
        { id: "language", title: "Language", icon: <Globe />, value: "English", href: "/settings/language" },
      ],
    },
    {
      id: "clients",
      label: "Recent clients",
      footer: "Swipe a row to the left to archive it.",
      items: [
        {
          id: "c01",
          title: "Acme Corp",
          description: "San Francisco · Nathan Price",
          avatar: { name: "Acme Corp" },
          value: "$12,480.00",
          swipe: {
            trailing: [
              { id: "archive", label: "Archive", icon: <Archive />, tone: "neutral", destructive: true, onSelect: () => onDelete("c01") },
              { id: "delete", label: "Delete", icon: <Trash2 />, tone: "danger", destructive: true, onSelect: () => onDelete("c01") },
            ],
          },
        },
      ],
    },
  ]
  return <List groups={groups} />
}

// parts, for rows the items API does not cover (same file, same look):
// ListRoot takes every appearance and behavior prop of List (variant, size, dividers, radius, tone, color,
//   selectionMode + selection props, sortable + onReorder, swipeable / swipeWithMouse / fullSwipe, onAction(id),
//   labels, classNames) plus keys / disabledKeys (read from the rows when left out); role list, or listbox;
// ListGroup { label, footer, refreshing } holds the variant's surface and animates rows in and out;
// ListItem takes every ListItemData field as a prop plus selected / onSelectedChange (standalone rows),
//   asChild (your router Link as the row), interactive and size; children replace the generated content;
// content parts: ListItemLeading, ListItemIcon { tone, color, variant }, ListItemContent, ListItemTitle,
//   ListItemDescription, ListItemTrailing (sits above the stretched pressable, so controls stay clickable);
// ListItemActions { items: MenuEntry[], open, onOpenChange, title, label } is the ⋯ menu (a sheet on phones);
// ListSortableHandle { label } is the grip of a sortable row (renders nothing outside a sortable list);
// SwipeActions { leading, trailing, disabled, swipeWithMouse, fullSwipe, onOpenChange, onRemovingChange }
//   wraps any row; DEFAULT_LIST_LABELS holds the English strings to spread a translation over
import { Star } from "lucide-react"
import {
  ListRoot, ListGroup, ListItem, ListItemLeading, ListItemIcon, ListItemContent, ListItemTitle,
  ListItemDescription, ListItemTrailing, ListItemActions,
} from "@/components/ui/list"

export function Favorites({ rows }: { rows: { id: string; name: string; city: string; total: string }[] }) {
  return (
    <ListRoot variant="separated" size="lg" aria-label="Favorite clients">
      <ListGroup label="Favorites">
        {rows.map((row) => (
          <ListItem key={row.id} id={row.id} title={row.name} href={`/clients/${row.id}`}>
            <ListItemLeading>
              <ListItemIcon tone="warning" variant="solid">
                <Star />
              </ListItemIcon>
            </ListItemLeading>
            <ListItemContent>
              <ListItemTitle>{row.name}</ListItemTitle>
              <ListItemDescription>{row.city}</ListItemDescription>
            </ListItemContent>
            <ListItemTrailing className="tabular-nums">{row.total}</ListItemTrailing>
            <ListItemActions items={[{ id: "open", label: "Open", href: `/clients/${row.id}` }]} />
          </ListItem>
        ))}
      </ListGroup>
    </ListRoot>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `items?` | `ListItemData[]` |  | The rows: { id, title, textValue?, description?, leading?, icon?, iconTone?, iconColor?, avatar?: { src?, name, status? }, trailing?, value?, badge?, href?, onSelect?, disabled?, tone?: "neutral" \| "danger" \| "brand", chevron?, actions?: MenuEntry[], swipe?: { leading?: SwipeAction[], trailing?: SwipeAction[] } }. trailing may hold controls (a Switch); the row then gets a stretched pressable so nothing nests in a button. |
| `groups?` | `ListGroupData[]` |  | { id, label?, footer?, items }[]: grouped rows with a small header and a footer note (iOS settings). Replaces items; sorting stays within a group. |
| `variant?` | `"inset" \| "plain" \| "separated" \| "card"` | `"inset"` | inset: the iOS grouped panel (a raised surface with hairlines); plain: full-bleed rows with hairlines, no container; separated: each row its own raised panel with an 8px gap; card: one flat gray tile with hairlines. |
| `size?` | `"sm" \| "md" \| "lg"` | `"md"` | Row height 44 / 56 / 64px with matching padding, avatars (32 / 40 / 48px) and icon tiles. Every size keeps a 44px target on touch. |
| `dividers?` | `"full" \| "inset" \| "none"` | `"inset"` | Hairlines between rows. inset starts them at the text, after the icon or avatar (iOS); full spans the row. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg"` | `"md"` | Corner radius of the panel (inset, card) or of each row (separated): 0 / 12 / 16 / 24px. First and last rows follow it. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The selection color: the checkbox fill, the check mark and the highlight tint. |
| `color?` | `string` | `""` | Any CSS color for the selection; replaces the tone. |
| `iconVariant?` | `"soft" \| "solid" \| "plain"` | `"soft"` | Icon tiles: a soft tint (gray, or the icon tone), a solid fill (the iOS settings look, with iconColor), or the bare icon. |
| `selectionIndicator?` | `"auto" \| "checkbox" \| "check" \| "highlight"` | `"auto"` | How a selected row shows: a leading checkbox, a trailing check (iOS pickers) or the row fill alone (with aria-selected). auto is a checkbox for multiple and a check for single. |
| `actionsVisibility?` | `"hover" \| "always"` | `"hover"` | The ⋯ row menu button: revealed on hover and focus with a mouse (always visible on touch), or always shown. |
| `selectionMode?` | `"none" \| "single" \| "multiple"` | `"none"` | none: navigation rows in the Tab order (labelled groups become labelled lists). single / multiple: a listbox with roving focus, Space to toggle, Shift ranges and Ctrl / Cmd + A (through useSelection); give it an aria-label. Inside an option the ⋯ button is pointer-only and Shift + F10 opens the menu. |
| `selectedKeys?` | `string[]` |  | Controlled selection, in display order. Pair with onSelectedKeysChange. |
| `defaultSelectedKeys?` | `string[]` | `"[]"` | Selection while uncontrolled. |
| `onSelectedKeysChange?` | `(keys: string[]) => void` |  | Fires with the selected ids in display order. |
| `loading?` | `boolean` | `false` | Skeleton rows in the row shape (a leading circle when the rows have avatars or icons) inside the same container, with aria-busy. |
| `refreshing?` | `boolean` | `false` | Keeps the rows and runs a thin indeterminate bar along the top edge of the first group (in the selection tone), with aria-busy. For pull to refresh and background reloads. |
| `skeletonCount?` | `number` | `4` | How many skeleton rows show while loading. |
| `emptyState?` | `ReactNode` |  | Shown when there are no rows; defaults to <EmptyState preset="empty" size="sm" title={labels.empty} />. |
| `error?` | `ReactNode` |  | Replaces the rows with the error EmptyState, this text as its description. |
| `onRetry?` | `() => (void \| Promise<unknown>)` |  | Adds Try again to the error state; a returned promise shows it loading. |
| `showChevron?` | `boolean` | `true` | Navigation rows (href or onSelect) end in a chevron. item.chevron overrides it per row. |
| `sortable?` | `boolean` | `false` | Drag handles (dnd-kit): mouse after 8px, touch after a 250ms press, keyboard with Space and arrows, plus Alt + Up / Down on a focused row or grip. The lifted row floats with a shadow and a slight tilt; groups reorder within themselves. |
| `onReorder?` | `(ids: string[], move: { id: string; from: number; to: number }) => void` |  | Fires after a drop or an Alt + Arrow move with the new order of that group. The list keeps the new order until you pass new items. |
| `swipeable?` | `boolean` | `true` | Rows with swipe actions can be swiped on touch. Off: the actions stay in the ⋯ menu and the long-press sheet. |
| `swipeWithMouse?` | `boolean` | `false` | Mouse drags swipe too. Off, desktop uses the ⋯ menu and right click, so text and clicks behave as usual. |
| `fullSwipe?` | `boolean` | `true` | Swiping past 60% of the row runs the outermost action (iOS swipe to delete), with a haptic tick when it arms. |
| `onAction?` | `(id: string, item: ListItemData) => void` |  | Row activation (click, tap, Enter), after the item's own onSelect. |
| `getActions?` | `(item: ListItemData) => (MenuEntry[] \| undefined)` |  | Row menu entries per item, instead of item.actions. The menu also lists the row's swipe actions. |
| `labels?` | `Partial<ListLabels>` |  | English defaults (DEFAULT_LIST_LABELS): actions "More actions for {item}", select, dragHandle "Drag to reorder {item}", pickedUp, moved "{item} moved to position {n} of {total}.", dropped, cancelled, empty, loading, sheetTitle, cancel. |
| `classNames?` | `Partial<Record<ListSlot, string>>` |  | Extra classes per part: group, header, items, footer, item, row, select, leading, icon, body, content, title, description, trailing, value, chevron, actions, handle, swipe, swipeAction, overlay, refreshing, empty. |
| `className?` | `string` |  | Classes for the root. Override the swipe surface with [--list-bg:var(--card)] when the list sits on another surface. |

## Parts

### ListRoot (component)

The outer part of a hand-built list. Takes every appearance and behavior prop of List (variant, size, dividers, radius, tone, color, selection, sortable with onReorder, swipe settings, onAction, labels, classNames) plus keys and disabledKeys, read from the rows when left out. Renders role list, or listbox when rows are selectable.

### ListGroup (component)

A group of rows on the variant's surface, with an optional header and footer note; it animates rows in and out.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label?` | `ReactNode` |  | A small header above the rows. |
| `footer?` | `ReactNode` |  | A note under the rows. |
| `refreshing?` | `boolean` | `false` | An indeterminate hairline on the top edge while the rows reload. |

### ListItem (component)

One row. Takes every ListItemData field as a prop (title, description, icon, avatar, value, badge, href, onSelect, actions, swipe...); children replace the generated content.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id?` | `string` |  | The row key for selection, reorder and swipe; generated when left out. |
| `selected?` | `boolean` |  | Selected state for a standalone row outside a selectable ListRoot. |
| `onSelectedChange?` | `(selected: boolean) => void` |  | Fires when a standalone row is toggled. |
| `asChild?` | `boolean` | `false` | Renders the single child (your router Link or an anchor) as the row. |
| `interactive?` | `boolean` |  | Forces the hover and press look; automatic when the row has href, onSelect, actions or selection. |
| `size?` | `"sm" \| "md" \| "lg"` |  | Overrides the list's row size. |

### ListItemLeading (component)

The start slot of a row, for an icon tile, an avatar or a thumbnail.

### ListItemIcon (component)

The rounded icon tile, sized by the row.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` |  | Tile tint; the neutral gray tile by default. |
| `color?` | `string` |  | Any CSS color; replaces the tone. |
| `variant?` | `"soft" \| "solid" \| "plain"` |  | A tinted tile, a filled tile (the iOS settings look) or just the icon. |

### ListItemContent (component)

The text column of a row, holding the title and the description.

### ListItemTitle (component)

The row title, one line with an ellipsis.

### ListItemDescription (component)

The muted line under the title.

### ListItemTrailing (component)

The end slot for a value, a badge, a switch or a time. It sits above the row's stretched pressable, so controls inside stay clickable.

### ListItemActions (component)

The more-actions menu of a row, a bottom sheet on phones. Without items the children render as they are, for your own buttons.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `items?` | `MenuEntry[]` |  | The menu entries. |
| `open?` | `boolean` |  | Controlled open state. |
| `onOpenChange?` | `(open: boolean) => void` |  | Fires when the menu opens or closes. |
| `title?` | `ReactNode` |  | The phone sheet's header. |
| `label?` | `string` |  | aria-label of the menu button; {item} is the row name. |

### ListSortableHandle (component)

The drag grip of a sortable row; renders nothing outside a sortable list.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label?` | `string` |  | aria-label of the grip; defaults to the dragHandle label. |

### SwipeActions (component)

The swipe wrapper, usable around any row: actions sit underneath at both edges and uncover as the row slides.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `leading?` | `SwipeAction[]` |  | Revealed by a swipe toward the end (right in left-to-right layouts). |
| `trailing?` | `SwipeAction[]` |  | Revealed by a swipe toward the start. |
| `disabled?` | `boolean` |  | Turns swiping off. |
| `swipeWithMouse?` | `boolean` |  | Lets mouse drags swipe too; defaults to the list's setting. |
| `fullSwipe?` | `boolean` |  | Past 60% the outermost action runs; defaults to the list's setting. |
| `onOpenChange?` | `(side: "leading" \| "trailing" \| null) => void` |  | Fires when a side opens or the row closes. |
| `onRemovingChange?` | `(removing: boolean) => void` |  | A destructive action slid the row out (true), or it came back (false). |

### DEFAULT_LIST_LABELS (constant)

The English strings (more actions, select, drag handle, reorder announcements, empty, loading, sheet title, cancel). Spread it to translate a few. Type: ListLabels.

### ListItemData (type)

One row of the items API: id, title, description, leading, icon with iconTone or iconColor, avatar, trailing, value, badge, href, onSelect, disabled, tone, chevron, actions and swipe.

### SwipeAction (type)

{ id, label, icon?, tone?, color?, onSelect, destructive? }; a destructive action slides the row out and the caller removes the item.

## Examples

- **iOS settings**: Inset groups with solid icon tiles, switches, values with chevrons and a danger row.
- **Contacts picker**: selectionMode="multiple": checkboxes, avatars, Shift ranges and select all.
- **Single choice**: A trailing check that draws itself, like an iOS picker, with prices formatted in USD.
- **Sortable**: Drag handles with a lifted overlay; Alt + Up / Down on a focused grip moves its row.
- **Separated orders**: Each row its own panel, with an amount and a status badge.
- **Loading and empty**: Skeletons in the row shape, then the rows; an empty list shows an EmptyState.
- **Sizes**: 44, 56 and 64px rows with 32, 40 and 48px avatars.
- **Compound parts**: ListRoot, ListGroup and ListItem with a brand highlight on a flat card.

## See also

- [Table](https://wingo-ui.com/components/table) ([Markdown](https://wingo-ui.com/components/table.md))
- [Task List](https://wingo-ui.com/components/task-list) ([Markdown](https://wingo-ui.com/components/task-list.md))
- [File List](https://wingo-ui.com/components/file-list) ([Markdown](https://wingo-ui.com/components/file-list.md))
- [Dropdown Menu](https://wingo-ui.com/components/dropdown-menu) ([Markdown](https://wingo-ui.com/components/dropdown-menu.md))
- [Empty State](https://wingo-ui.com/components/empty-state) ([Markdown](https://wingo-ui.com/components/empty-state.md))
- [useSelection](https://wingo-ui.com/components/use-selection) ([Markdown](https://wingo-ui.com/components/use-selection.md))

---

Canonical: https://wingo-ui.com/components/list
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
