KQ-1588 · BOW/RIBBON
Bow Ribbon
A message printed on a satin ribbon that comes in from the left edge and ties itself into a bow: the ribbon slides along its own path like a strip pulled off a spool, twisting as it comes — its width is w·cos θ, the plain back shows where it turns over, and a sheen band runs along the satin as the turn meets the light — while a soft wave rolls along it and dies away. When the message has passed, the head of the ribbon runs on round the bow's track at the knot, so the end loops up into a right loop, a left loop and a tail; the knot wraps on with springs.flick, a second tail drops in, and the bow lands with a small bounce on springs.recoil. Everything is SVG in em units, so it sizes with the type and the server renders the tied still frame.
Waylight · gift card
Happy birthday, Juno
60 to spend at the Coldbrook Bookshop, from all of us.
wrapped · scroll it into view
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/bow-ribbon
Props
- text
- string
- default "Thank you for coming"
- The message printed on the ribbon; graphemes are kept whole.
- as
- "h1" | "h2" | "h3" | "p" | "div" | "span"
- default "h2"
- The element the message renders as.
- ribbonWidth
- number
- default 1.6
- The ribbon's width, in em of the type: 1.2 is snug, 2.4 generous.
- sheen
- number
- default 0.6
- How strongly the satin catches the light as it turns, 0 (matte) to 1.
- bowSize
- number
- default 1
- The bow's size relative to the ribbon, 0.6 to 1.6; the box grows to hold it.
- color
- string
- default "oklch(from var(--danger) 0.56 calc(c * 0.9) h)"
- The satin's colour, any CSS colour; the default is a pigment that looks the same in both themes.
- textColor
- string
- default "oklch(from var(--danger) 0.97 0.02 h)"
- The printed message's colour.
- twists
- number
- default 2
- Full turns the ribbon makes as it comes in, 0 to 3.
- trigger
- "mount" | "inView" | "hover" | "manual"
- default "inView"
- When the ribbon runs: on mount, when first on screen, on hover or keyboard focus, or only when replayKey changes.
- once
- boolean
- default true
- Run once, or again each time it returns to view (inView) or is hovered.
- delay
- number
- default 0
- Wait before it starts, ms.
- duration
- number
- default 2400
- How long the ribbon takes to come in and tie, ms (900 to 6000); the bow's landing adds about half a second.
- replayKey
- string | number
- default —
- Change it to run the ribbon again.
- onStart
- () => void
- default —
- Fires when a run begins.
- onComplete
- () => void
- default —
- Fires when the bow has settled (at once under reduced motion).
- className
- string
- default —
- Sets the type size (the ribbon, bow and box are sized in em) and places it.
| Prop | Type | Default | Description |
|---|---|---|---|
| text | string | "Thank you for coming" | The message printed on the ribbon; graphemes are kept whole. |
| as | "h1" | "h2" | "h3" | "p" | "div" | "span" | "h2" | The element the message renders as. |
| ribbonWidth | number | 1.6 | The ribbon's width, in em of the type: 1.2 is snug, 2.4 generous. |
| sheen | number | 0.6 | How strongly the satin catches the light as it turns, 0 (matte) to 1. |
| bowSize | number | 1 | The bow's size relative to the ribbon, 0.6 to 1.6; the box grows to hold it. |
| color | string | "oklch(from var(--danger) 0.56 calc(c * 0.9) h)" | The satin's colour, any CSS colour; the default is a pigment that looks the same in both themes. |
| textColor | string | "oklch(from var(--danger) 0.97 0.02 h)" | The printed message's colour. |
| twists | number | 2 | Full turns the ribbon makes as it comes in, 0 to 3. |
| trigger | "mount" | "inView" | "hover" | "manual" | "inView" | When the ribbon runs: on mount, when first on screen, on hover or keyboard focus, or only when replayKey changes. |
| once | boolean | true | Run once, or again each time it returns to view (inView) or is hovered. |
| delay | number | 0 | Wait before it starts, ms. |
| duration | number | 2400 | How long the ribbon takes to come in and tie, ms (900 to 6000); the bow's landing adds about half a second. |
| replayKey | string | number | — | Change it to run the ribbon again. |
| onStart | () => void | — | Fires when a run begins. |
| onComplete | () => void | — | Fires when the bow has settled (at once under reduced motion). |
| className | string | — | Sets the type size (the ribbon, bow and box are sized in em) and places it. |