Swipe to confirm
Free · MITReact Native · ExpoSlide to confirm: the thumb follows the finger 1:1, the track fills behind it and the label fades, a tick marks the point of no return, and at the end the track gathers into a spinner and opens into the result.
Installation
$ npx shadcn@latest add https://goodui.dev/native/r/swipe-to-confirm.jsonThen run npx expo install --fix so react-native-gesture-handler, react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/swipe-to-confirm.
Usage
import { SwipeToConfirm } from "@/components/goodui-native/swipe-to-confirm/swipe-to-confirm";
export function PayBar({ pay }: { pay: () => Promise<void> }) {
return <SwipeToConfirm label="Slide to pay €24.00" confirmedLabel="Paid" onConfirm={pay} />;
}When to use
- Committing money or something hard to undo where a stray tap must not count: pay, send, place order.
- Starting something physical or time-bound: start a ride, check in, clock in, end a workout.
- Confirming at the bottom of a review screen, within thumb reach.
- Deleting or other destructive actions inside a list: use hold-to-confirm, which works in place without horizontal travel.
- Ordinary actions: use button; sliding for everything is tiring.
- Inside horizontally scrolling or swipeable containers (carousels, swipe-actions rows).
Examples
Start a ride, success tone, stays confirmed
<SwipeToConfirm tone="success" size="lg" label="Slide to start ride" confirmedLabel="Ride started" resetAfter={false} onConfirm={startRide} />A request that can fail
<SwipeToConfirm label="Slide to send €1,240" errorLabel="Card declined" onConfirm={() => api.send(transfer)} />API reference
| Prop | Type | Default |
|---|---|---|
| label* What sliding does, for example "Slide to pay €24.00". | string | — |
| onConfirm* Runs when the thumb reaches the end. Return a promise: the track becomes a spinner until it settles. | () => unknown | — |
| tone `accent` for payments and sends, `success` for check-ins, `danger` for destructive actions. | "accent" | "success" | "danger" | "accent" |
| size 56 or 64 pt tall. | "md" | "lg" | "md" |
| confirmedLabel Shown with the check once confirmed, for example "Paid". | string | — |
| errorLabel Shown when the promise rejects, before the thumb springs back. Long reasons wrap to two lines. Same as `labels.error`. | string | — |
| icon Thumb icon. Defaults to an arrow. A function receives the icon color. | ReactNode | ((color: string) => ReactNode) | — |
| disabled Dims the control and ignores presses. | boolean | false |
| resetAfter Return to the start this many ms after confirming. `false` keeps the confirmed state. | number | false | 1800 |
| accessibilityHint Read by screen readers after the label. Same as `labels.hint`. | string | — |
| labels Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. | Partial<SwipeToConfirmLabels> | — |
| style Style for the outer container. | StyleProp<ViewStyle> | — |
| testID Identifier for end-to-end tests. | string | — |
Motion and haptics
- Touch down: the thumb gives to 0.92 on the snappy spring before anything moves.
- Drag: the thumb tracks the finger 1:1 with a little rubber band before the start; the track's fill grows behind it and darkens from faint to solid; the label drifts along and fades by 60%.
- Release past 85% (or a flick that would reach the end from past halfway) snaps to the end with the finger's velocity; earlier, it springs back on smooth with the same velocity.
- Pending: the track gathers into a circle on the morph spring around a spinner. Result: it opens back on morph to green with a drawn check, or to red with the error label and a shake, then springs back.
- A tap on the track nudges the thumb forward and back on the bouncy spring to teach the gesture.
- Reduced motion: the thumb still follows the finger, but releases jump into place, the track never changes shape, there is no nudge or shake, and states crossfade.
- Haptic: select when the thumb crosses the point of no return
- Haptic: confirm when confirmed (after the promise resolves), error when it rejects
- The drag runs entirely on the UI thread through Gesture Handler and Reanimated. React renders only on status changes.
Accessibility
- The track is one button with the label; disabled and busy are in accessibilityState and the label follows the state (in progress, done, error).
- Screen readers cannot slide: the activate action (double tap) confirms directly. On the web, Enter or Space on the focused track does the same.
- Results are announced with AccessibilityInfo.announceForAccessibility.
- Default hint: 'Slide the handle to the end to confirm'.
Notes for AI
- Return the promise from onConfirm; the component shows pending, success and error. Do not add a loading flag.
- errorLabel can carry the reason: it shows at subhead size and wraps to two lines inside the track (longer text ends in an ellipsis), so keep it under about 50 characters.
- It stretches to its parent's width. Place it in the bottom third of the screen with 16 pt gutters.
- Label starts with 'Slide to' and names the amount or outcome. confirmedLabel is the past tense: 'Paid', 'Sent', 'Checked in'.
- resetAfter={false} keeps the confirmed state, for flows that navigate away afterwards.
- Requires GestureHandlerRootView at the app root (see the foundation setup).
Related
Confirms an irreversible action by holding: ink fills from the leading edge, the label inverts where the ink passes, and haptics tick at every quarter.
A press-responsive button with haptics that runs async actions on its own: it gathers into a spinner, then opens into a success or error state.
A sheet that rests at detents, follows the finger 1:1, settles with velocity and hands drags off to its scroll view.