Hold to Confirm

Animated

A destructive action gated behind a deliberate hold.

View Source

#Installation

npx @dinachi/cli@latest add hold-to-confirm

#Usage

tsx
import { HoldToConfirm } from "@/components/ui/hold-to-confirm"
tsx
<HoldToConfirm onConfirm={deleteProject} confirmedLabel="Deleted">
  Hold to delete
</HoldToConfirm>

#Examples

#Variants

VariantBehaviourUse it when
fillA bar sweeps the button from the left, behind the label.Most cases. The most legible of the three, and the only one that reads at a glance across a room.
ringA stroke closes around the button.Square icon-only buttons, where there is no width for a bar to travel.
borderThe button's own outline draws itself.A hold inside dense UI. Nothing moves behind the label, so it is the quietest option.

#When to use it

A hold buys the same deliberation as a confirmation dialog without taking over the screen, and it leaves the action where the user found it.

Use it forNot for
Deleting a project, revoking a key, ending a session.Anything repeated. A hold costs a second and a half every time, and a control hit ten times a day is resented by the third.
Actions with no undo.Anything reversible. An undo is faster to offer and cheaper to ignore.

#Behaviour

  • Asymmetric timing. Progress takes duration to complete but unwinds in 200ms. Committing should feel considered, backing out should feel free.
  • Resumes, never restarts. Release unwinds from the current position, so letting go and pressing again does not start over.
  • Survives a small drag. Pointer capture keeps events arriving once the finger leaves the button.

#Accessibility

  • Space and Enter hold, and key up releases. Auto-repeat is ignored, so a held key is one hold rather than a restart on every repeat.
  • Blur cancels. A hold that continues once the button is unfocused is one the user can no longer watch.
  • Give a ring button an aria-label. It has an outline and an icon, and neither is a name.
  • data-holding and data-confirmed expose both states for styling, so press feedback does not have to ride on :active.
  • Reduced motion drops the press scale and keeps the progress. The progress is the affordance, not decoration: a hold that counts invisibly has no feedback at all.

#API Reference

PropTypeDefaultDescription
variant"fill" | "ring" | "border""fill"How progress is drawn
durationnumber1600Milliseconds the user must hold to confirm
onConfirm() => voidFired once the hold completes
resetAfternumber1600Milliseconds the confirmed state is held before resetting. Set 0 to stay confirmed
confirmedLabelReactNodeReplaces the label while confirmed
fillClassNamestringClass applied to the progress fill
renderRenderPropRender as a different element