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.
Waylight Sessions
Night Signals
Hall B · doors 19:00
Thu 18 Sep · 19:30
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.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | The ticket's body: what is being confirmed. |
| stubContent | ReactNode | — | 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 | "Tear to confirm" | The tear handle's accessible name. |
| confirmedLabel | string | "Confirmed" | Shown where the stub was, and the handle's name once torn. |
| torn / defaultTorn | boolean | — / 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 | — | Fires with true from the drag or key that tore the stub off. |
| onConfirm | () => void | — | Called once when the stub drops free: the action is confirmed. |
| onProgressChange | (progress: number) => void | — | Fires as the visitor tears, with how far along the seam, 0 to 1 (two decimals). |
| resistance | number | 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 | 0.28 | The stub's share of the ticket's width, 0.15 to 0.5. |
| curl | number | 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 | 9 | Spacing of the punched holes along the seam, in px; the bridges between them are where the tear catches. |
| paper | string | "var(--card)" | The body's paper, any CSS colour. |
| stubPaper | string | "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 | "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" | "md" | The ticket's height (112, 132 or 152px), notch size and padding; it is w-full up to 520px. |
| sound | boolean | 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 | false | Disables the handle. |
| className | string | — | Classes for the root. |