Swipe actions
Free · MITReact Native · ExpoRow 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.
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.Installation
$ npx shadcn@latest add https://goodui.dev/native/r/swipe-actions.jsonThen run npx expo install --fix so react-native-gesture-handler, react-native-reanimated match your Expo SDK. Files land in components/goodui-native/swipe-actions.
- Wrap the app in GestureHandlerRootView and ThemeProvider.
Usage
import { SwipeActions, swipeActionColors } from "@/components/goodui-native/swipe-actions/swipe-actions";
<SwipeActions
accessibilityLabel={`${mail.from}, ${mail.subject}`}
leading={[{ key: "read", label: "Read", tone: "accent", onPress: () => markRead(mail.id) }]}
trailing={[
{ key: "delete", label: "Delete", tone: "danger", destructive: true, onPress: () => remove(mail.id) },
{ key: "flag", label: "Flag", tone: "warning", onPress: () => flag(mail.id) },
]}
>
<MailRow mail={mail} />
</SwipeActions>When to use
Use it for
- Lists where people act on many rows quickly: inbox, tasks, notifications.
- One or two frequent actions per row, with the most common one at the edge.
Reach for something else
- The only way to reach an action: every swipe action is also reachable from the screen reader actions menu, but sighted people need a visible path too, such as a detail screen or a menu.
- Rows inside a horizontally scrolling container.
API reference
| Prop | Type | Default |
|---|---|---|
| children* The row content. It slides over the actions, so give it an opaque background. | ReactNode | — |
| leading Revealed by swiping right (left under RTL), at the start edge. The first action sits at the screen edge. | SwipeAction[] | [] |
| trailing Revealed by swiping left (right under RTL), at the end edge. The first action sits at the screen edge. | SwipeAction[] | [] |
| fullSwipe A long swipe runs the edge action. Default `true`. | boolean | true |
| contentStyle Style of the row surface that slides over the actions. It needs an opaque background. | StyleProp<ViewStyle> | — |
| onPress Tapping the row runs this, for example to open the conversation. An open row closes instead. The row highlights on touch down and becomes a button for screen readers and the keyboard (Enter or Space on the web), so keep other buttons out of its content when you pass it. | () => void | — |
| accessibilityLabel Screen reader label. On the web a pressable row is a button named by the text inside it, so the label applies on native only there: put words the row does not show (", 2 unread") in the content as visually hidden text instead. | string | — |
| testID Identifier for end-to-end tests. | string | — |
Motion and haptics
- Actions share the revealed width evenly as you drag; labels stay hidden until they fit, then fade in centered.
- Past 55% of the row width the edge action grows over the tray on the snappy spring.
- Releases snap open or closed with the drag velocity. Destructive actions slide the row out, then collapse its height.
- With onPress a muted highlight fades in 50 ms after touch down (so a scroll does not flash the row) and fades out over 320 ms; moving more than 10 pt hands the touch to the swipe.
- Haptic: select when crossing the full-swipe threshold in either direction
- Haptic: tap when an action runs, delete for a destructive one
- Gesture, widths and collapse run on the UI thread.
Accessibility
- The row is one accessible element with an accessibilityActions entry per action, so screen reader users reach every action without swiping.
- Action buttons themselves are hidden from screen readers to avoid duplicates.
- With onPress the row is a button: double-tap (activate) opens it on native, and on the web it takes keyboard focus and Enter or Space presses it.
- On the web a pressable row is named by the text inside it (accessibilityLabel applies on native only there, since a label that differs from the visible text fails WCAG 2.5.3); add words the row does not show, such as ', 2 unread', as visually hidden text in the content.
Notes for AI
- The first action in leading or trailing sits at the screen edge and is the one a long swipe runs.
- Mark removals destructive and remove the item in onPress; the row collapses before onPress is called.
- Children need an opaque background (the default is the theme surface) so the actions stay hidden.
- Color icons with swipeActionColors(tone, colors).fg.
- Only one row stays open at a time; you do not need to manage that.
- For a row that opens something (a conversation, a message), pass onPress instead of wrapping the content in a Pressable: a Pressable inside the swipe claims the pointer on the web and the swipe never starts. Tapping an open row closes it instead of calling onPress.