KQ-1420 · SILK/RIBBONS
Silk Ribbons
Silk ribbons stream from the pointer on one Canvas 2D layer: each is a chain of 32 links whose head rides the pointer on a spring blended between springs.drift and springs.snap, with every link following the one ahead at its length and held smooth by a bending spring, and the ribbons differ in length, stiffness and drag so a sweep fans them out. When a link catches up its overtaking motion turns sideways, so as the pointer stops the ribbons overshoot, wind into loops and unwind, and a press whips an S-wave down each chain; each ribbon tapers and twists, darkening edge-on and shifting colour between its two faces like iridescent silk. With nobody pointing they fly a slow figure-eight, keyboard focus sends them to circle the focused control, and Enter or Space on it whips them there.
Fernworks · spring silks
Cut from the softest light.
in flight · move across or press Tab
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/silk-ribbons
Props
- children
- React.ReactNode
- default —
- Content laid over the ribbons, such as a headline. It keeps every click, focus and key.
- count
- number
- default 5
- How many ribbons stream from the pointer, 1 to 8.
- length
- number
- default 220
- A ribbon's length in px; each varies around it by variance, and none outgrows 1.6× the box's shorter side.
- colors
- "opal" | "ember" | "tide" | "mono" | readonly string[]
- default "opal"
- The silk: a named palette from the theme tokens, or your own CSS colours the ribbons shift through along their length.
- width
- number
- default 11
- A ribbon's widest, just below the head, in px.
- taper
- number
- default 0.85
- How much a ribbon thins toward its tail, 0 (even) to 1 (a fine point).
- stiffness
- number
- default 0.5
- How tightly the heads follow and the silk holds its line, 0 (springs.drift, loose) to 1 (springs.snap, taut).
- variance
- number
- default 0.5
- How different the ribbons are from each other in length, stiffness and drag, 0 to 1.
- curl
- number
- default 0.5
- How much the ribbons wind into loops as they catch up, 0 to 1.
- twist
- number
- default 1.5
- Twists along each ribbon; where it turns edge-on it narrows and darkens.
- sheen
- number
- default 0.6
- A soft specular streak down faces turned to the light, 0 to 1.
- spread
- number
- default 12
- How wide the fan of heads is either side of the pointer, across its direction of travel, in px.
- whip
- boolean
- default true
- A press whips the ribbons, throwing a loop down each one.
- ambient
- boolean
- default true
- With nobody pointing, the ribbons fly a slow figure-eight through the box.
- followFocus
- boolean
- default true
- Keyboard focus on a control inside sends the ribbons to circle it.
- interactive
- boolean
- default true
- Follow the pointer.
- paused
- boolean
- default false
- Hold the ribbons still and stop the frame loop.
- seed
- number
- default 5
- Seed for every ribbon's length, spring, twist and colour.
- dpr
- number
- default 2
- The highest device pixel ratio the canvas draws at (never above 2).
- onWhip
- (point: { x: number; y: number }) => void
- default —
- Fires when a press (or Enter/Space on a control inside) whips the ribbons, with the point in the box.
- className
- string
- default —
- Classes for the box. Give it a height; children are laid out inside it.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode | — | Content laid over the ribbons, such as a headline. It keeps every click, focus and key. |
| count | number | 5 | How many ribbons stream from the pointer, 1 to 8. |
| length | number | 220 | A ribbon's length in px; each varies around it by variance, and none outgrows 1.6× the box's shorter side. |
| colors | "opal" | "ember" | "tide" | "mono" | readonly string[] | "opal" | The silk: a named palette from the theme tokens, or your own CSS colours the ribbons shift through along their length. |
| width | number | 11 | A ribbon's widest, just below the head, in px. |
| taper | number | 0.85 | How much a ribbon thins toward its tail, 0 (even) to 1 (a fine point). |
| stiffness | number | 0.5 | How tightly the heads follow and the silk holds its line, 0 (springs.drift, loose) to 1 (springs.snap, taut). |
| variance | number | 0.5 | How different the ribbons are from each other in length, stiffness and drag, 0 to 1. |
| curl | number | 0.5 | How much the ribbons wind into loops as they catch up, 0 to 1. |
| twist | number | 1.5 | Twists along each ribbon; where it turns edge-on it narrows and darkens. |
| sheen | number | 0.6 | A soft specular streak down faces turned to the light, 0 to 1. |
| spread | number | 12 | How wide the fan of heads is either side of the pointer, across its direction of travel, in px. |
| whip | boolean | true | A press whips the ribbons, throwing a loop down each one. |
| ambient | boolean | true | With nobody pointing, the ribbons fly a slow figure-eight through the box. |
| followFocus | boolean | true | Keyboard focus on a control inside sends the ribbons to circle it. |
| interactive | boolean | true | Follow the pointer. |
| paused | boolean | false | Hold the ribbons still and stop the frame loop. |
| seed | number | 5 | Seed for every ribbon's length, spring, twist and colour. |
| dpr | number | 2 | The highest device pixel ratio the canvas draws at (never above 2). |
| onWhip | (point: { x: number; y: number }) => void | — | Fires when a press (or Enter/Space on a control inside) whips the ribbons, with the point in the box. |
| className | string | — | Classes for the box. Give it a height; children are laid out inside it. |