KQ-1375 · BUOYANT/LINE
Buoyant Line
Scroll-float text: as the line scrolls up through its container's viewport, its letters float up from below their places left to right, each on its own share of the scroll and its own spring — from springs.snap toward springs.drift as the lag grows, with a bob at the surface — swaying, tinted and faded while submerged, and they sink back if you scroll up. Progress is read from the nearest scroll container, or set directly, and the element's bottom edge is the waterline; the keyboard path is the container's own scrolling.
KQ-1375 · BUOYANT/LINE
rising 0% · scroll the journal
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/buoyant-line
Props
- text
- string
- default —
- The line. Read once by assistive technology, and copied as written.
- as
- "h1" | "h2" | "h3" | "h4" | "p" | "span" | "div"
- default "p"
- The element the line renders as.
- rise
- number
- default 1
- How deep the letters start below their places, em.
- lag
- number
- default 0.5
- How strung out the rise is, 0 (together, brisk) to 1 (one after another, slow to follow).
- range
- number
- default 0.5
- How much of the viewport's height the line scrolls through while its letters rise, 0.1 to 1.
- start
- number
- default 0.92
- Where the rise begins, as a fraction of the viewport's height from its top (1 is the bottom edge).
- sway
- number
- default 8
- How far a submerged letter tilts as it sways, degrees.
- bob
- number
- default 0.5
- How much the letters bob as they reach the surface, 0 (none) to 1.
- tint
- string
- default "var(--accent-bright)"
- The colour a submerged letter takes before it surfaces into ink.
- fade
- boolean
- default true
- Fade the letters with depth.
- progress
- number
- default —
- Drive the rise yourself, 0 to 1; overrides scrolling.
- once
- boolean
- default false
- Once every letter has surfaced, stay surfaced.
- scrollContainer
- React.RefObject<HTMLElement | null>
- default —
- The scroll container to read; defaults to the nearest scrollable ancestor, or the window.
- onProgressChange
- (progress: number) => void
- default —
- The line's progress through its rise, 0 to 1 in steps of 0.01, as it changes.
- onComplete
- () => void
- default —
- Every letter has surfaced (again).
- className
- string
- default —
- Classes for the line element; size, weight and colour are inherited.
| Prop | Type | Default | Description |
|---|---|---|---|
| text | string | — | The line. Read once by assistive technology, and copied as written. |
| as | "h1" | "h2" | "h3" | "h4" | "p" | "span" | "div" | "p" | The element the line renders as. |
| rise | number | 1 | How deep the letters start below their places, em. |
| lag | number | 0.5 | How strung out the rise is, 0 (together, brisk) to 1 (one after another, slow to follow). |
| range | number | 0.5 | How much of the viewport's height the line scrolls through while its letters rise, 0.1 to 1. |
| start | number | 0.92 | Where the rise begins, as a fraction of the viewport's height from its top (1 is the bottom edge). |
| sway | number | 8 | How far a submerged letter tilts as it sways, degrees. |
| bob | number | 0.5 | How much the letters bob as they reach the surface, 0 (none) to 1. |
| tint | string | "var(--accent-bright)" | The colour a submerged letter takes before it surfaces into ink. |
| fade | boolean | true | Fade the letters with depth. |
| progress | number | — | Drive the rise yourself, 0 to 1; overrides scrolling. |
| once | boolean | false | Once every letter has surfaced, stay surfaced. |
| scrollContainer | React.RefObject<HTMLElement | null> | — | The scroll container to read; defaults to the nearest scrollable ancestor, or the window. |
| onProgressChange | (progress: number) => void | — | The line's progress through its rise, 0 to 1 in steps of 0.01, as it changes. |
| onComplete | () => void | — | Every letter has surfaced (again). |
| className | string | — | Classes for the line element; size, weight and colour are inherited. |