KQ-1485 · PAPER/BALL
Paper Ball
A discard control you crumple and throw: holding the note folds a 6×6 mesh of the sheet inward along seeded creases into a lumpy paper ball, the facets that turn over going dark and the handwriting twisting with the paper, while letting go early springs it flat on springs.snap. A formed ball is tossed on a true ballistic arc into a wire basket, or dragged 1:1 and thrown with the release velocity into a small room whose floor, walls and the basket's rim lips it bounces off; a miss rolls to rest and glides home on springs.glide, and a ball that drops between the lips rocks the basket on springs.recoil. Space or Enter held crumples like a press, Delete crumples and tosses in one motion, and Escape smooths the sheet out.
note up · hold it to crumple
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/paper-ball
Props
- text
- string
- default "Call the printer about Friday's proofs"
- What the note says, wrapped to three short lines on the sheet.
- label
- string
- default "Discard note"
- The note's accessible name, read before its text.
- discardedLabel
- string
- default "Note discarded"
- The accessible name once the note is in the basket.
- discarded / defaultDiscarded
- boolean
- default — / false
- Whether the note is in the basket. Setting it back to false puts a fresh note up; setting it true drops the ball straight in.
- onDiscardedChange
- (discarded: boolean) => void
- default —
- Fires with true when the ball lands in the basket.
- onThrow
- (result: "in" | "miss") => void
- default —
- Fires when a throw ends: in the basket, or at rest on the floor.
- onCrumpleChange
- (crumpled: boolean) => void
- default —
- Fires when the ball forms and when the sheet is smoothed out again.
- onProgressChange
- (progress: number) => void
- default —
- Fires while the hold charges or the sheet smooths, with how crumpled it is in tenths (0–10).
- hold
- number
- default 600
- How long a hold takes to make a ball, in ms; a re-press continues from where the sheet is.
- crumple
- number
- default 0.8
- How tight the ball is, 0 to 1: a loose wad (bigger, softer creases) to a small, hard, deeply creased ball.
- arc
- number
- default 0.5
- The toss's height, 0 to 1: a flat line drive to a high lob. The arc is solved so it always drops through the mouth.
- bounce
- number
- default 0.5
- How lively the floor, walls and rim lips are, 0 to 1 (restitution 0.2 to 0.72).
- gravity
- number
- default 1800
- Gravity in scene units per second squared; the scene is 300 units wide.
- seed
- number
- default 7
- The crease pattern and the ball's lumps.
- paper
- string
- default "oklch(from var(--warn) 0.93 0.07 h)"
- The note's paper, any CSS colour; a pigment at fixed lightness so it reads the same in both themes.
- ink
- string
- default "oklch(from var(--warn) 0.36 0.07 h)"
- The handwriting's colour.
- size
- "sm" | "md" | "lg"
- default "md"
- The scene's max width: 260, 300 or 340px; its height follows the 5:3 scene.
- sound
- boolean
- default false
- Play a crinkle at every fifth of the hold, a crisp one on the toss and a thud on every landing, scaled by how hard it hits.
- disabled
- boolean
- default false
- Takes the note out of the tab order and ignores presses.
- className
- string
- default —
- Classes for the root, which is w-full up to its size's max width.
| Prop | Type | Default | Description |
|---|---|---|---|
| text | string | "Call the printer about Friday's proofs" | What the note says, wrapped to three short lines on the sheet. |
| label | string | "Discard note" | The note's accessible name, read before its text. |
| discardedLabel | string | "Note discarded" | The accessible name once the note is in the basket. |
| discarded / defaultDiscarded | boolean | — / false | Whether the note is in the basket. Setting it back to false puts a fresh note up; setting it true drops the ball straight in. |
| onDiscardedChange | (discarded: boolean) => void | — | Fires with true when the ball lands in the basket. |
| onThrow | (result: "in" | "miss") => void | — | Fires when a throw ends: in the basket, or at rest on the floor. |
| onCrumpleChange | (crumpled: boolean) => void | — | Fires when the ball forms and when the sheet is smoothed out again. |
| onProgressChange | (progress: number) => void | — | Fires while the hold charges or the sheet smooths, with how crumpled it is in tenths (0–10). |
| hold | number | 600 | How long a hold takes to make a ball, in ms; a re-press continues from where the sheet is. |
| crumple | number | 0.8 | How tight the ball is, 0 to 1: a loose wad (bigger, softer creases) to a small, hard, deeply creased ball. |
| arc | number | 0.5 | The toss's height, 0 to 1: a flat line drive to a high lob. The arc is solved so it always drops through the mouth. |
| bounce | number | 0.5 | How lively the floor, walls and rim lips are, 0 to 1 (restitution 0.2 to 0.72). |
| gravity | number | 1800 | Gravity in scene units per second squared; the scene is 300 units wide. |
| seed | number | 7 | The crease pattern and the ball's lumps. |
| paper | string | "oklch(from var(--warn) 0.93 0.07 h)" | The note's paper, any CSS colour; a pigment at fixed lightness so it reads the same in both themes. |
| ink | string | "oklch(from var(--warn) 0.36 0.07 h)" | The handwriting's colour. |
| size | "sm" | "md" | "lg" | "md" | The scene's max width: 260, 300 or 340px; its height follows the 5:3 scene. |
| sound | boolean | false | Play a crinkle at every fifth of the hold, a crisp one on the toss and a thud on every landing, scaled by how hard it hits. |
| disabled | boolean | false | Takes the note out of the tab order and ignores presses. |
| className | string | — | Classes for the root, which is w-full up to its size's max width. |