Hold Button
loomui/hold-buttonFreeComponent
A button that fires only after a deliberate press and hold, with a fill sweeping across to count out the wait.
Live preview
live · rendered by the registry
Rendered by loomui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://loomui.design/r/hold-button.jsonSource
1"use client"23import * as React from "react"45import { cn } from "@/lib/utils"67export interface HoldButtonProps extends React.ComponentProps<"button"> {8 /** Milliseconds the button has to be held down before it fires. */9 duration?: number10 /** Called once, when a hold runs the whole way through. */11 onHold?: () => void12 /** Fill that sweeps across the button. Keep it translucent so the label survives. */13 color?: string14}1516/** How long the fill takes to run back out when a hold is abandoned. */17const RETURN_MS = 26018/** How long a completed fill sits at full before it clears. */19const SETTLE_MS = 20020/** How long the completed fill takes to fade off. */21const CLEAR_MS = 2402223/** Fast at first, then easing to a stop. The fill draining should feel let go of. */24function easeOutCubic(t: number) {25 return 1 - Math.pow(1 - t, 3)26}2728export function HoldButton({29 children,30 className,31 duration = 1200,32 onHold,33 color = "color-mix(in oklch, var(--primary, currentColor) 22%, transparent)",34 disabled,35 style,36 ref: forwardedRef,37 ...props38}: HoldButtonProps) {39 const ref = React.useRef<HTMLButtonElement>(null)4041 // The button needs its own ref to write the fill to, so a caller's ref is42 // pointed at the same node rather than replacing it.43 React.useImperativeHandle(44 forwardedRef,45 () => ref.current as HTMLButtonElement46 )47 const frame = React.useRef(0)48 const settle = React.useRef<ReturnType<typeof setTimeout>>(undefined)49 const [holding, setHolding] = React.useState(false)50 const [clearing, setClearing] = React.useState(false)51 // A completed button stays pressed until its fill has cleared. Springing52 // back to full size the instant the action lands reads as a bounce, and53 // fights whatever the caller is doing with the button at the same moment.54 const [settling, setSettling] = React.useState(false)55 const spent = React.useRef(false)5657 // Progress is a ref written straight to a custom property. A fill that58 // re-rendered React sixty times a second to cross the button would be absurd.59 const state = React.useRef({60 progress: 0,61 filling: false,62 last: 0,63 from: 0,64 since: 0,65 })6667 const write = React.useCallback(() => {68 ref.current?.style.setProperty(69 "--hold-progress",70 `${state.current.progress}`71 )72 }, [])7374 const tick = React.useCallback(75 (now: number) => {76 const current = state.current7778 if (current.filling) {79// … truncated
What it pulls in
Other registry items
Files it writes
More from loomui
All 91 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Aurora Backdropaurora-backdrop | loomui | Components | Free | no deps | |
| Bento Gridbento-grid | loomui | Components | Free | no deps | |
| Card Stackcard-stack | loomui | Components | Free | no deps | |
| Compare Slidercompare-slider | loomui | Components | Free | no deps | |
| Confetti Buttonconfetti-button | loomui | Components | Free | no deps | |
| Count Upcount-up | loomui | Components | Free | no deps | |
| Credit Cardcredit-card | loomui | Components | Free | no deps | |
| Drawerdrawer | loomui | Components | Free | 1 dep |
