Media & effects
Scroll Velocity Text
Direction-aware kinetic text that expands and contracts with scroll
detent-scroll-velocity-text
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 4
- Wave
- phase-4-cinematic
- Group
- Media & effects
3.8 KB gzip for the class module
Variants
Scroll this page down and up: the text in the main preview expands and fades out with downward scroll, winds the same progress back up, and holds where it is when scrolling stops. It is one continuous text node, never per-letter spans.
A quieter configuration with a later fade (fade-start="0.7") and a smaller scale ceiling. Both use the
documented defaults for smoothing-ms (400) and transform-origin (center center).
prefers-reduced-motion: reduce locks every instance to its resting frame: no scroll tracking, no frame,
no transform. An empty text is the documented disabled state and behaves the same way.
Recorded states
disabledrecorded state; no setterexpandedrecorded state; no setterreduced motionrecorded state; no setter
Usage
Import
import '/components/scroll-velocity-text/define.js';
// or register it yourself
import { DetentScrollVelocityText } from '/components/scroll-velocity-text/scroll-velocity-text.js';
Attributes
expand-distancefade-startintensitymax-letter-spacingmax-scalemin-letter-spacingsmoothing-mstexttransform-origin
Properties
asscrollContainerRef
Slots
No slots are recorded for this component.
Events
No events are recorded for this component.
Example
Runnable example: site/examples/scroll-velocity-text.ts. The snippet below is generated from the catalogued attributes.
<detent-scroll-velocity-text
expand-distance="500"
fade-start="0.4"
intensity="1"
max-letter-spacing="1"
max-scale="1.6"
min-letter-spacing="0"
smoothing-ms="400"
text
transform-origin="center center"
></detent-scroll-velocity-text>
Property-only values
const element = document.querySelector('detent-scroll-velocity-text');
if (element !== null) {
// as: keyof JSX.IntrinsicElements
element.as = 'span';
// scrollContainerRef: RefObject<HTMLElement | null>
element.scrollContainerRef = undefined;
}
Source
The pinned upstream source for this row:
src/components/ui/Scrollvelocitytext.tsxsha256 e948f3c06de6…
Native files
The native module and its artifacts:
src/components/scroll-velocity-text/scroll-velocity-text.tssrc/components/scroll-velocity-text/styles.csssrc/components/scroll-velocity-text/define.tsdocs/components/scroll-velocity-text.mdsite/examples/scroll-velocity-text.tstests/browser/scroll-velocity-text.spec.tsevidence/components/scroll-velocity-text.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
expand-distance | expandDistance | number | 500 | Kebab-case attribute; camelCase property. |
fade-start | fadeStart | number | 0.4 | Kebab-case attribute; camelCase property. |
intensity | intensity | number | 1 | Kebab-case attribute; camelCase property. |
max-letter-spacing | maxLetterSpacing | number | 1 | Kebab-case attribute; camelCase property. |
max-scale | maxScale | number | 1.6 | Kebab-case attribute; camelCase property. |
min-letter-spacing | minLetterSpacing | number | 0 | Kebab-case attribute; camelCase property. |
smoothing-ms | smoothingMs | number | 400 | Kebab-case attribute; camelCase property. |
text | text | string | — | Kebab-case attribute; camelCase property. |
transform-origin | transformOrigin | string | 'center center' | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
as | keyof JSX.IntrinsicElements | 'span' | Property-only: assign from script; not accepted as an attribute. |
scrollContainerRef | RefObject<HTMLElement | null> | — | Property-only: assign from script; not accepted as an attribute. |
Events
No custom events are recorded for this component.
Slots
No slots are recorded for this component.
CSS customisation
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- as accepts the documented allow-list instead of any intrinsic element; an unknown value falls back to span with one warning.
- scrollContainerRef accepts an element as well as the upstream React ref shape.
- The upstream alias export Scrollvelocitytext is dropped; the pure helpers are the only additional named exports.
- The pinned motion is an exponential damping loop, not a spring: the shared spring helper is deliberately not used because a spring would change the recorded velocity-to-offset mapping, so no PC-10.4 approval is pending.
- The disabled state is nothing to track: empty text pins the rest frame and schedules no frame.
- className and Tailwind typography become the three custom properties plus the text part.
What it does
Kinetic text that expands its letter spacing, scales up and fades out while the page (or a container) scrolls down, winds the same progress back up, and holds position when scrolling stops. The text stays one continuous native text node.
Upstream behaviour
- Direction-aware scroll physics: expands on downward scroll, winds back on upward scroll, and holds position on idle
- Zero per-frame React re-renders: progress is written directly to the DOM via requestAnimationFrame batching
- GPU-accelerated CSS transitions with cubic-bezier easing for buttery smooth kinetic tracking
- Screen-reader safe: rendered as an unbroken native text node rather than fragmented per-letter DOM spans
- Customizable expansion range, letter spacing bounds (em), scale multiplier, and fade threshold
- Flexible polymorphic tag rendering via the "as" prop (h1, h2, span, p, etc.) with custom transform-origin
- Supports both global window scroll and scoped scrollable container refs
- Built-in prefers-reduced-motion detection automatically locks text to resting state
Differences from upstream
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- as accepts the documented allow-list instead of any intrinsic element; an unknown value falls back to span with one warning.
- scrollContainerRef accepts an element as well as the upstream React ref shape.
- The upstream alias export Scrollvelocitytext is dropped; the pure helpers are the only additional named exports.
- The pinned motion is an exponential damping loop, not a spring: the shared spring helper is deliberately not used because a spring would change the recorded velocity-to-offset mapping, so no PC-10.4 approval is pending.
- The disabled state is nothing to track: empty text pins the rest frame and schedules no frame.
- className and Tailwind typography become the three custom properties plus the text part.