Swipe to confirm

Free · MITReact Native · Expo

Slide 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.

View as Markdown
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-to-confirm.json

Then 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

Use it for
  • 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.
Reach for something else
  • 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

PropTypeDefault
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.
booleanfalse
resetAfter
Return to the start this many ms after confirming. `false` keeps the confirmed state.
number | false1800
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).