KQ-1228 · PUNCH/CARD
Punch Card
A loyalty card you punch: each press sinks the next printed spot under the die on the flick spring, then cuts a real hole through the card (an SVG mask, so the table and the card's own shadow show through), the card jolts and recovers on recoil, and a paper chad carrying its piece of the print pops toward you and falls away under gravity on an ease-in tween. The final punch lights the reward with a recoil landing and a ring, then the card turns over on the snap spring to its redeem side; Redeem turns it back, and the holes give way to a fresh card at the instant it is edge-on. The count is a role="meter" and the punch is a real button, so Space or Enter presses and cuts through the same path, and focus follows the card when it turns.
Coldbrook Coffee
Loyalty · No. 0417
Ari Lund
Nine punches, one free
Space or Enter punches the next spot.
6 of 9 punched · 3 to the free one
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/punch-card
Props
- brand
- string
- default —
- The café or shop, printed on the card's band.
- reward
- string
- default "One free drink"
- What a full card is worth.
- member
- string
- default —
- The holder's name, printed at the foot of the card.
- number
- string
- default —
- The card's number, printed on the band.
- code
- string
- default —
- The redeem code on the back. Defaults to a deterministic code from the brand, the member and the cycle.
- label
- string
- default —
- The card's accessible name. Defaults to "{brand} loyalty card".
- value / defaultValue
- number
- default 0
- How many spots are punched, 0 to holes; controlled or uncontrolled.
- onValueChange
- (value: number) => void
- default —
- Fires from the press that punched a spot, or from Redeem with 0.
- onRedeem
- () => void
- default —
- Fires when the reward is redeemed.
- holes
- number
- default 9
- How many punches earn the reward, 6 to 12; the grid re-lays out in two rows.
- shape
- "round" | "star" | "heart"
- default "round"
- The printed spot, the hole and the chad.
- chad
- boolean
- default true
- A paper chad pops out of each hole and falls away. Off: a clean cut.
- sound
- boolean
- default false
- A click at each cut, rising in pitch across the card, a pop as the reward lights, and a click on Redeem.
- disabled
- boolean
- default false
- The card can be read but not punched or redeemed.
- className
- string
- default —
- Classes for the root.
| Prop | Type | Default | Description |
|---|---|---|---|
| brand | string | — | The café or shop, printed on the card's band. |
| reward | string | "One free drink" | What a full card is worth. |
| member | string | — | The holder's name, printed at the foot of the card. |
| number | string | — | The card's number, printed on the band. |
| code | string | — | The redeem code on the back. Defaults to a deterministic code from the brand, the member and the cycle. |
| label | string | — | The card's accessible name. Defaults to "{brand} loyalty card". |
| value / defaultValue | number | 0 | How many spots are punched, 0 to holes; controlled or uncontrolled. |
| onValueChange | (value: number) => void | — | Fires from the press that punched a spot, or from Redeem with 0. |
| onRedeem | () => void | — | Fires when the reward is redeemed. |
| holes | number | 9 | How many punches earn the reward, 6 to 12; the grid re-lays out in two rows. |
| shape | "round" | "star" | "heart" | "round" | The printed spot, the hole and the chad. |
| chad | boolean | true | A paper chad pops out of each hole and falls away. Off: a clean cut. |
| sound | boolean | false | A click at each cut, rising in pitch across the card, a pop as the reward lights, and a click on Redeem. |
| disabled | boolean | false | The card can be read but not punched or redeemed. |
| className | string | — | Classes for the root. |