- Accordion
- Action Bar
- Action Toast
- Alert Banner
- Alert Dialog
- Alert
- Alphabet Index
- Amount Field
- Aspect Ratio
- Attachment
- Availability List
- Avatar
- Badge
- Bank Card
- Breadcrumb
- Bubble
- Button Group
- Button
- Calendar
- Camera Frame
- Card Design Picker
- Card Details
- Card Peek
- Card Stack
- Card
- Carousel
- Chart
- Chat Thread
- Checkbox
- Chip Group
- Choice Tile
- Code Block
- Code Dots
- Collapsible
- Combobox
- Command
- Context Menu
- Copy Field
- Countdown
- Country Select
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Direction
- Document Paper
- Drawer
- Dropdown Menu
- Empty
- Entity Avatar
- Field
- File Trigger
- File Upload Item
- Hover Card
- Input Group
- Input OTP
- Input
- Item
- Kbd
- Key Value
- Keypad
- Label
- Live Region
- Marker
- Message Scroller
- Message
- Money Amount
- Native Select
- Number Field
- Pagination
- Percent Split Editor
- Phone Field
- Popover
- Progress
- QR Code
- Questionnaire
- Radio Group
- Range Histogram
- Rating
- Resizable
- Scroll Area
- Search Field
- Select
- Separator
- Share Bar
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Sortable List
- Spinner
- Status Timeline
- Step Header
- Steps List
- Swipe Row
- Switch
- Table
- Tabs
- Text Field
- Textarea
- Tier Ladder
- Toggle Group
- Toggle
- Token Search Field
- Tooltip
- Trace
- Transaction Row
- Usage Meter
Swipe a row to the left
"use client"
import * as React from "react"The SwipeRow component wraps a list row, such as a transaction or a scheduled payment, and uncovers up to three action tiles when the user swipes it to the side. A short swipe opens the tiles, and a long swipe runs the action marked isFullSwipe. The tiles are ordinary buttons in the tab order, so nothing depends on the gesture. Use it for quick, repeatable actions on rows in a long list; when an action needs a choice or a confirmation, open a menu or a sheet from the row instead.
Installation#
pnpm dlx shadcn@latest add @dots/swipe-row
Usage#
import {
SwipeRow,
SwipeRowAction,
SwipeRowActions,
SwipeRowContent,
} from "@/components/ui/swipe-row"<SwipeRow>
<SwipeRowContent>
<TransactionRow title="Coffee Point" amount={-4.5} onPress={openDetails} />
</SwipeRowContent>
<SwipeRowActions aria-label="Coffee Point actions">
<SwipeRowAction variant="destructive" isFullSwipe onAction={remove}>
<TrashIcon />
Delete
</SwipeRowAction>
</SwipeRowActions>
</SwipeRow>Composition#
Use the following composition to build a swipe row:
SwipeRow
├── SwipeRowContent
└── SwipeRowActions
└── SwipeRowAction (up to three)Features#
- A short swipe opens the tiles, a long swipe past 60% of the row runs the full-swipe action
- A vertical drag scrolls the page, so the row never blocks scrolling
- A drag, or a tap that only closes the row, never also presses the row underneath
- Keyboard focus on a tile opens the row; Escape closes it and returns focus to the row, and moving focus away also closes it
- Three tile variants:
secondary,primary, anddestructive - Controlled or uncontrolled open state, and right-to-left layouts
Open by default#
Set defaultOpen to show the tiles on first render. Use variant="primary" for the main action of the row and variant="destructive" for delete; the rest stay secondary.
"use client"
import {
SwipeRow,Single action#
A row can have one tile. Keyboard users reach it by tabbing past the row: the row opens when the tile gets focus, and Escape closes it and returns focus to the row.
"use client"
import * as React from "react"Controlled#
Pass open and onOpenChange to open and close the row from outside, for example from a button that teaches the gesture.
"use client"
import * as React from "react"Disabled#
Set isDisabled to turn the gesture off, for example while a transfer is still sending. The tiles stay reachable by keyboard.
"use client"
import {
SwipeRow,Accessibility#
Each SwipeRowAction renders a React Aria Button, so it responds to mouse, touch, keyboard, and screen reader activation without the swipe. SwipeRowActions is a role="group" that opens the row when focus enters it, so the focused tile is always visible. Escape closes the row and does not also close a sheet or popover around it.
Make the row focusable#
The row content must hold a focusable element, such as a TransactionRow with onPress or href. When a tile closes the row from the keyboard, or Escape closes it, focus moves to that element. Without one, focus has nowhere to go: a keyboard action or Escape leaves the row open until focus leaves it, and Escape passes through to a sheet or popover around the row.
Label the group#
SwipeRowActions defaults to aria-label="Actions". Name the row it belongs to, such as "Coffee Point actions", so screen reader users know which row the tiles act on.
Tile labels#
The visible text of a tile names its button. Keep it short, such as "Pin" or "Delete", and keep the icon decorative.
Moving focus after a remove#
After an action the row closes and focus moves from the tile back to the focusable element in the row. An action that removes the row, such as delete or archive, must move focus itself, for example to the next row or to an undo button. The demo at the top moves focus to Undo after a delete, and back to the restored row after the last undo.
API Reference#
SwipeRow#
The root. Holds the open state and the gesture.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | Whether the tiles are shown. Pair with onOpenChange. |
defaultOpen | boolean | false | Whether the tiles are shown on first render when uncontrolled. |
onOpenChange | (open: boolean) => void | - | Called when the row opens or closes. |
isDisabled | boolean | false | Turns the swipe gesture off. The tiles stay reachable by keyboard. |
className | string | - | Additional classes to apply to the root element. |
...props | Omit<React.ComponentProps<"div">, "onChange"> | - | Props spread to the <div>. |
SwipeRowContent#
The row itself, such as a TransactionRow or an Item. It slides with the swipe. It must hold a focusable element, see Make the row focusable.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional classes to apply to the content. |
...props | React.ComponentProps<"div"> | - | Props spread to the <div>. |
SwipeRowActions#
The tiles behind the row, up to three. Focus entering the group opens the row.
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | "Actions" | Names the group of tiles for assistive tech. |
className | string | - | Additional classes to apply to the group. |
...props | React.ComponentProps<"div"> | - | Props spread to the <div>. |
SwipeRowAction#
One tile: an icon above a short label. Renders a React Aria Button.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "secondary" | "primary" | "destructive" | "secondary" | The tile style. |
onAction | () => void | - | Runs on press, and on a full swipe when isFullSwipe is set. |
isFullSwipe | boolean | false | A long swipe stretches this tile across the row and runs onAction. One per row. |
closeOnAction | boolean | true | Closes the row after the action runs. |
className | string | - | Additional classes to apply to the tile. |
...props | Omit<ButtonProps, "className" | "onPress"> | - | React Aria Button props, such as isDisabled or aria-label. |