Hold to confirm
Free · MITReact Native · ExpoConfirms an irreversible action by holding: ink fills from the leading edge, the label inverts where the ink passes, and haptics tick at every quarter.
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/hold-to-confirm.jsonThen run npx expo install --fix so react-native-reanimated match your Expo SDK. Files land in components/goodui-native/hold-to-confirm.
Usage
import { HoldToConfirm } from "@/components/goodui-native/hold-to-confirm/hold-to-confirm";
<HoldToConfirm confirmedLabel="Project deleted" onConfirm={() => api.deleteProject(id)}>
Delete project
</HoldToConfirm>When to use
Use it for
- Irreversible or costly actions: delete a project, send money, cancel a subscription.
- Places where a confirmation dialog would interrupt more than it helps.
Reach for something else
- Frequent, reversible actions: use button, and offer Undo through toast.
- Actions that must be reachable with one quick tap.
API reference
| Prop | Type | Default |
|---|---|---|
| children* What happens, as a verb: "Delete project". | string | — |
| onConfirm* Runs when the hold completes. Return a promise to show a spinner until it settles. | () => unknown | — |
| duration How long to hold, in ms. Default 1200. | number | 1200 |
| tone `danger` for destructive actions, `accent` for costly but positive ones such as sending money. | "danger" | "accent" | "danger" |
| confirmedLabel Shown with the check once confirmed, for example "Deleted". | string | — |
| hint Hint under the hold, read by screen readers too. Same as `labels.hint`. | string | — |
| disabled Dims the control and ignores presses. | boolean | false |
| resetAfter Return to the idle state this many ms after confirming. `false` keeps the confirmed state. | number | false | 1800 |
| labels Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. | Partial<HoldToConfirmLabels> | — |
| style Style for the outer container. | StyleProp<ViewStyle> | — |
| testID Identifier for end-to-end tests. | string | — |
Motion and haptics
- The ink fills linearly over the duration. Letting go early drains it on the smooth spring.
- The label exists twice, in the base and ink colors, clipped by the ink, so it inverts exactly where the ink is.
- Haptic: select at 25, 50 and 75 percent
- Haptic: warning at the end of the hold
- Haptic: confirm when confirmed, error when the promise rejects
- Progress runs on the UI thread; the early-release decision is made there so a release at the last moment never cancels a completed hold.
Accessibility
- Screen reader users cannot hold: a double tap runs the action directly, and the hint says what happens.
- Busy state while a returned promise is pending; the result is announced.
Notes for AI
- Label with a verb and the object: 'Delete project', not 'Delete'.
- Return a promise from onConfirm to show a spinner until it settles.
- Pass resetAfter={false} when the screen navigates away after confirming.