Slide to Confirm — Drag Slider HTML CSS JS Snippet
Slide to Confirm · Forms · Plain HTML, CSS & JS · Live preview
What's included
Features
translateX and the fill uses scaleX, so the motion is compositor-smooth and survives Tailwind/React export (which animate transforms, not raw width).down records the pointer-to-handle offset, so grabbing the handle anywhere does not snap it to the cursor — it tracks naturally.maxPos() reads the track and handle sizes on each use, so the end threshold stays correct even if the layout resizes.window keep the drag alive when the pointer leaves the track mid-slide.complete(); anything short animates back, enforcing a deliberate full slide.done flag blocks further dragging so the action cannot double-fire, and the track turns green with a check label..animate class adds transition: transform only for snap-back and confirm, keeping the active drag perfectly 1:1.e.touches or e.clientX, so it works identically on phones and desktops.About this UI Snippet
Slide to Confirm — Drag-to-Commit Handle, Snap-Back & Confirmed Lock State

"Slide to confirm" is the deliberate-action control: instead of a one-tap button that is easy to hit by accident, the user must drag a handle all the way across a track to commit. It is the right pattern for irreversible or high-stakes actions — sending a payment, deleting an account, dispatching an order, unlocking a device. This snippet implements it in plain HTML, CSS, and vanilla JavaScript with full touch and mouse support: a draggable handle, a fill that tracks the drag, snap-back if released early, and a locked confirmed state.
Transform-based dragging (smooth everywhere)
The handle moves with transform: translateX and the green fill grows with transform: scaleX from a left origin — both are compositor-friendly transforms, so the drag is smooth and, importantly, the animation survives a Tailwind/React export unchanged (utility frameworks animate transforms but not raw width). During an active drag the position is set directly with no transition for 1:1 finger tracking; only the snap-back and confirm use a .animate class that adds transition: transform.
Accurate pointer math
On press, down records the offset between the pointer and the handle's current position, so the handle does not jump to the cursor. move then sets the position as pointerX − offset, clamped between 0 and maxPos() (the track width minus the handle and padding, measured live so it stays correct if the layout changes). The move and up listeners live on window, so dragging continues even if the pointer leaves the track.
Threshold, snap-back, and lock
On release, up checks whether the handle reached the end (within a few pixels of maxPos()). If it did, complete() locks the control: it snaps the handle fully right, fills the track green, changes the label to "✓ Payment confirmed", and reveals a reset link. If it did not, the handle and fill animate back to the start — the action is *not* committed, which is the whole point of requiring a full, intentional slide. A done flag blocks further dragging once confirmed so the action cannot fire twice.
Replayable
A reset button (hidden until confirmation) calls resetSlide, animating everything back to the initial state so you can demo it repeatedly or re-arm the control after, say, a failed transaction.
Because the commit happens only in complete(), wiring it to a real action is a one-line change. Pair this with a confirm dialog for typed confirmations, an order summary before payment, or a download button for progress-based actions.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to work out the pointer math or the threshold logic by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why down() records an offset between the pointer and the handle's current position instead of just using the raw pointer coordinate, or why maxPos() is recalculated live rather than cached once. The same assistant can help optimize it, for instance checking whether the move handler could throttle its style writes on very high-frequency pointer events without hurting the 1:1 tracking feel. It is just as useful for extending the control: ask it to add a keyboard-accessible fallback with arrow-key filling and a role of slider with aria-valuenow, support a hold-at-the-end confirmation delay before locking, or add a haptic vibration call on mobile when the slide completes. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "slide to confirm" drag-to-commit control in plain HTML, CSS, and JavaScript, supporting both mouse and touch — no libraries.
Requirements:
- A pill-shaped track containing a label, a fill element, and a circular draggable handle positioned at the left edge.
- The handle's position must be driven only by a CSS transform: translateX, and the fill's growth only by transform: scaleX from a left transform-origin — do not animate raw width or left/right positioning, so the motion stays compositor-smooth and survives being exported to a utility-class framework that only animates transforms.
- On pointer-down, record the offset between the pointer's coordinate and the handle's current position so that grabbing the handle anywhere on its face does not cause it to jump to the cursor location.
- On pointer-move, compute the new position as pointerCoordinate minus that recorded offset, clamped between zero and a live-measured maximum derived from the track's current width minus the handle's width and padding — recompute this maximum on each interaction rather than caching it once, so it stays correct if the layout changes.
- Attach the move and release listeners to the window, not just the track or handle, so a drag continues tracking correctly even if the pointer leaves the track's bounds mid-gesture.
- On release, if the handle's position is within a few pixels of the maximum, lock the control into a confirmed state: snap the handle fully to the end, fill the entire track, change the label text, and block any further dragging via a boolean flag. If released short of that threshold, animate both the handle and fill back to the start with a CSS transition that is only enabled during the snap-back and confirm moments, not during active dragging.
- Provide a reset control that reverses the confirmed state and animates everything back to the starting position so the interaction can be replayed.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA dark pill track appears with a white circular handle on the left and the label "Slide to confirm payment".
- 2Drag the handlePress the handle and drag right — it follows your finger 1:1 and a green fill grows behind it.
- 3Release earlyLet go before the end — the handle and fill spring back to the start and nothing is confirmed.
- 4Slide all the wayDrag to the far right and release — the track locks green, the label becomes "✓ Payment confirmed", and a reset link appears.
- 5Reset and replayClick "↺ Reset" — everything animates back so you can try again or re-arm the control.
- 6Wire your actionPut your real API call or navigation inside
complete()— it only runs on a full, intentional slide.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Put your code inside complete() — it is called only when the handle is released at the end of the track. For async actions (a payment API), keep the locked state during the request and, on failure, call resetSlide() with an error message so the user can slide again; on success, leave it locked or navigate away.
Transforms (translateX, scaleX) run on the compositor for smoothness, and — crucially for this site's framework exports — Tailwind's transition utility animates transform but not raw width. Using scaleX for the fill means the snap-back animation works identically in the HTML, React, Vue, and Tailwind versions.
up() confirms when pos >= maxPos() - 4 (within 4px of the end). Lower the tolerance for a stricter "must reach the very end" feel, or raise it to forgive a slightly short slide. You can also require holding at the end briefly before committing by adding a short timer in up().
Dragging alone is not accessible, so provide a keyboard path: make the handle a focusable <button> (it is) and support Enter/Space or Arrow-Right-to-fill as an alternative that calls complete(). Add role="slider" with aria-valuenow updated as it moves, and ensure the confirmed state is announced via the label text, not colour alone.
In React, store the position in a ref (not state, to avoid re-render per frame), attach pointer listeners in a useEffect with cleanup, and set the transform via the handle ref; flip a confirmed state in complete. In Vue, use template refs and onMounted/onUnmounted. In Angular, bind in ngAfterViewInit with @ViewChild. The transform math and snap-back CSS port unchanged.