> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cskn.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Lists and pages

> SwipeRow for swipe actions, VirtualList for long lists, and Pager for swipeable pages.

## SwipeRow

Swipe a row left to reveal actions, like a mail app's Delete and Archive, or right for leading actions such as Pin or Mark as read.

```tsx theme={null}
import { SwipeRow, GroupedRow, useToast } from '@cskn/ui';

<SwipeRow
  actions={[
    { label: 'Delete', tone: 'destructive', onPress: () => remove(item) },
    { label: 'Archive', onPress: () => archive(item) },
  ]}
  leadingActions={[{ label: 'Pin', tone: 'accent', onPress: () => pin(item) }]}
  fullSwipe
>
  <GroupedRow title={item.title} onPress={() => open(item)} />
</SwipeRow>
```

**actions** `SwipeAction[]` *required*

Revealed from the right, the first one outermost.

**leadingActions** `SwipeAction[]`

Revealed from the left by a swipe to the right.

**fullSwipe** `boolean` (default: false)

A swipe across most of the row runs the outermost action on that side without stopping at the buttons. A haptic marks the point past which letting go runs it. Off by default because the outermost action is often Delete; if you turn it on, pair Delete with an [undo toast](https://docs.cskn.dev/ui/components/feedback.md#undo-instead-of-confirm).

Each action has `label`, `onPress` and `tone?` (`'destructive'`, `'neutral'` or `'accent'`).

Vertical scrolling keeps working through the row, a drag never counts as a tap, and an open row closes when you touch anywhere else. Rows combine with [`ContextMenu`](https://docs.cskn.dev/ui/components/overlays#contextmenu) for long-press actions.

## VirtualList

Renders only the rows near the screen, so a list of 2,000 rows scrolls like one of 20. Row heights can differ; each row is measured once it's drawn.

```tsx theme={null}
import { GroupedVirtualSection, GroupedRow } from '@cskn/ui';

<GroupedVirtualSection
  header="All songs"
  items={songs}
  keyOf={(s) => s.id}
  estimate={60}
  renderItem={(s) => <GroupedRow title={s.title} subtitle={s.artist} onPress={() => play(s)} />}
/>
```

`GroupedVirtualSection` keeps the grouped card's hairlines and rounded corners and takes `header?`, `footer?` and `separatorInset?`. `VirtualList` is the same without the section.

**items** `T[]` *required*

The rows' data.

**keyOf** `(item) =\> string` *required*

A stable key per item.

**renderItem** `(item, index) =\> ReactNode` *required*

Draws a row.

**estimate** `number` (default: 56)

A row's height before it's measured, in px.

**overscan** `number` (default: 8)

Extra rows drawn beyond the visible ones at each end.

Inside a `StackScreen` it scrolls with the screen.

## Pager

Pages side by side that you swipe between, using the browser's snap scrolling so momentum feels native on both platforms.

```tsx theme={null}
import { Pager } from '@cskn/ui';

const [page, setPage] = useState(0);

<Pager
  value={page}
  onChange={setPage}
  tabs={['Overview', 'Details']}
  pages={[<Overview />, <Details />]}
/>
```

**pages** `ReactNode[]` *required*

The pages, each as wide as the pager.

**value** `number` *required*

The page showing.

**onChange** `(index: number) =\> void` *required*

A swipe settled on another page, or a tab or dot was tapped.

**tabs** `string[]`

Labels drawn above the pages: Material tabs whose indicator follows the finger on Android, a segmented control on iOS. Without them, page dots appear underneath.

**label** `string`

Accessible name of the tab row or the dots.

← and → move between pages. A sideways swipe that starts on the pager turns the page; the iOS edge swipe still goes back.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.