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.

KQ-1588 · BOW/RIBBON

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.