KQ-1486 · STUB/TEAR

Stub Tear

A confirmation ticket you tear: pulling down the perforation runs the tear front 1:1 with the finger, its edges ragged where the paper bridges between the punched holes give — each bridge catching until the finger is far enough past it, then letting go — while the torn flap of the stub hinges away about the front, lifting by its curl over a soft shadow. Let go short of the end and the seam springs closed on springs.snap (springs.glide for stiff paper) with the release velocity, a flick that would carry past the end finishes on springs.glide, and at the end the stub drops free on the exit ease and the action confirms. The handle is a real button: Enter or Space tears the whole seam in one motion.

KQ-1486 · STUB/TEAR

Waylight Sessions

Night Signals

Hall B · doors 19:00

Thu 18 Sep · 19:30

Admit oneF · 12Row · Seat
Drag down the perforation, or press Enter, to tear off the stub.

stub attached · tear it to confirm

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/stub-tear

Props

children
ReactNode
default —
The ticket's body: what is being confirmed.
stubContent
ReactNode
default —
The stub's face. Presentational: while it tears it is drawn twice, once hidden from assistive tech, so keep it free of controls and ids.
label
string
default "Tear to confirm"
The tear handle's accessible name.
confirmedLabel
string
default "Confirmed"
Shown where the stub was, and the handle's name once torn.
torn / defaultTorn
boolean
default — / false
Whether the stub has been torn off. A host setting it back to false brings the stub back on springs.glide.
onTornChange
(torn: boolean) => void
default —
Fires with true from the drag or key that tore the stub off.
onConfirm
() => void
default —
Called once when the stub drops free: the action is confirmed.
onProgressChange
(progress: number) => void
default —
Fires as the visitor tears, with how far along the seam, 0 to 1 (two decimals).
resistance
number
default 0.4
How hard the paper bridges catch, 0 to 1: a clean zip to a fibrous stick-slip at every bridge; above 0.6 the seam also closes without overshoot.
stub
number
default 0.28
The stub's share of the ticket's width, 0.15 to 0.5.
curl
number
default 0.5
How far the torn flap swings (7° to 18°) and lifts as it comes away, and how dark its underside shows, 0 to 1.
holes
number
default 9
Spacing of the punched holes along the seam, in px; the bridges between them are where the tear catches.
paper
string
default "var(--card)"
The body's paper, any CSS colour.
stubPaper
string
default "oklch(from var(--accent-bright) 0.94 0.035 h)"
The stub's paper; a pigment at fixed lightness so it reads the same in both themes.
stubInk
string
default "oklch(from var(--accent-bright) 0.34 0.09 h)"
Ink for the stub's face, chosen to read on the stub's pigment.
size
"sm" | "md" | "lg"
default "md"
The ticket's height (112, 132 or 152px), notch size and padding; it is w-full up to 520px.
sound
boolean
default false
Play a paper tick at every bridge that gives and a snap when the stub comes free; only in answer to a drag or key.
disabled
boolean
default false
Disables the handle.
className
string
default —
Classes for the root.