Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Text

Pro

Scroll Velocity for React

Rows of text that slide sideways without end and speed up as the page scrolls. For React with Base UI, when a landing page needs bold moving type that answers the scroll.

Framework

Scroll Velocity slides rows of text sideways without end, every other row the other way, and ties them to the page's scroll: scroll down and each row is pushed along its way, faster the faster you scroll; scroll up and it's held back, even turned round. Each row repeats its text enough to fill a wide screen, so it never shows a gap or a seam.

Use it for a bold band of words between the sections of a landing page, a portfolio's headline or a ticker. For text that flows along a curve, use Curved Loop. It's CSS only: keyframe animations slide the rows, and a scroll-driven animation adds the push from the page's scroll position, with no scroll listener and no animation library. Chrome, Edge and Safari 26 tie the rows to the scroll; in other browsers, such as Firefox, they simply slide. With reduced motion they hold still.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import { ScrollVelocity } from "@/components/ui/scroll-velocity";
<ScrollVelocity texts={["Build fast", "Ship faster"]} />

Demos

Rows

Each text in texts is a row, and every other row runs the other way. intensity is how hard scrolling pushes them: subtle, medium (the default, about half a pixel for each pixel scrolled) or strong, here. Each row is data-slot="scroll-velocity-row", so rows can be styled from outside: here every other one is muted.

Preview width

Ticker

separator draws a mark between copies, a part of its own (data-slot="scroll-velocity-separator"), and speed is the time a row takes to move by one character: 300 ms by default, 150 here for a quick ticker band.

Preview width

Accessibility

Screen readers read each row's text once, from a visually hidden copy; the moving copies are hidden from them. The rows stop sliding while the pointer is over them, for anyone reading slowly; scrolling still pushes them, since then the reader is the one moving them. With reduced motion they hold still, push and all.

FAQ

Which browsers tie the rows to the scroll?

Those with CSS scroll-driven animations: Chrome and Edge 115 and later, and Safari 26 and later. In the others, such as Firefox today, the rows slide at their own pace and ignore the scroll; nothing else changes.

How does it follow the scroll without a scroll listener?

The push is a CSS animation whose clock is the page's scroll position (animation-timeline: scroll(root)) rather than time: every stretch scrolled moves a row on by one copy. Added to the slide, the row speeds up while you scroll and settles back when you stop, without any script.

Does it follow a panel that scrolls on its own?

No: it follows the page's own scroll. Inside a panel that scrolls by itself, or a page in a frame, the rows simply slide.

Which way do the rows go?

The first leftwards, the next rightwards, and so on; on right-to-left text, each the other way, so it flows its reading way. Scrolling down pushes every row its own way, and scrolling up holds it back.

Why is there never a gap?

Each row repeats its text to at least 400 characters, more than a wide screen shows even in small type, and moves by exactly one copy before starting again, which looks just as it did. Set rows in the size you like: the copies scale with the font.

Can I set the speed in CSS?

Yes: --scroll-velocity-speed is the time a row takes to move by one character, and --scroll-velocity-intensity how many pixels a row moves for each pixel scrolled, on the component or any parent.

What happens with reduced motion?

The rows hold still and ignore the scroll, each showing its text from the start.

API Reference

ScrollVelocity

<span> data-slot="scroll-velocity"

PropType
textsstring[]

One row each, moving in turn one way and the other.

intensity?"subtle" | "medium" | "strong"

How hard scrolling pushes the rows: about a quarter, a half or all of the distance scrolled.

Type
"subtle" | "medium" | "strong"
Default
"medium"
separator?string

Drawn between one copy of a row's text and the next, such as ✦ or •.

speed?number

The time a row takes to move by one character, in milliseconds; lower is faster.

Default
300
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

CSS variableType
--scroll-velocity-intensitystring

How far a row moves for each pixel scrolled (0.5).

--scroll-velocity-speedstring

The time a row takes to move by one character (300ms).

All 97 text effects