Scroll velocity

Free · MIT

Rows of large text drift in opposite directions and speed up or flip with your scroll speed and direction.

MarkdownOpen in v0
Open in v0

Installation

$ npx shadcn@latest add https://goodui.dev/r/scroll-velocity.json

The file lands in components/goodui/scroll-velocity.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/scroll-velocity works too.

Usage

import { ScrollVelocity } from "@/components/goodui/scroll-velocity"

export function ServicesBand() {
  return (
    <section className="overflow-hidden py-16">
      <ScrollVelocity
        items={["Deep cleans · Move-outs · Offices", "Windows · Carpets · Post-construction"]}
        baseVelocity={30}
        rowClassName="text-5xl font-bold tracking-tight sm:text-7xl"
      />
    </section>
  )
}

When to use

Use it for
  • A bold band of large text between landing-page sections (services, cities, client types) that reacts to scrolling.
  • Agency and portfolio sites where scroll-linked motion is part of the brand.
  • Rows of custom content (logos, chips) that should drift and respond to scroll speed, using ScrollVelocityRow.
Reach for something else
  • A steady logo or testimonial ticker that should not react to scrolling: use marquee, which is CSS-only and pauses on hover.
  • Inside dense app screens: moving text competes with the data. Use a static heading.
  • Text people must read in full: it is constantly moving, so put essential copy in static text.
If you needUse
Rows of big wordsScrollVelocity with items={[…]}
Custom content per row (logos, chips)ScrollVelocityRow with children
Calmer driftbaseVelocity={20}
Constant speed, no scroll reactionvelocityFactor={0} (or use marquee)
Inside a scrolling panelscrollContainer={panelRef}

Examples

A row of client logos with ScrollVelocityRow

<ScrollVelocityRow baseVelocity={-25}>
  {logos.map((l) => (
    <img key={l.name} src={l.src} alt={l.name} className="mx-8 h-8 w-auto opacity-70" />
  ))}
</ScrollVelocityRow>

API reference

ScrollVelocity

PropTypeDefault
items*
One string per row. Rows alternate direction.
string[]—
baseVelocity
Pixels per second at rest.
number40
scrollContainer
Scrollable element to track. Defaults to the window.
React.RefObject<HTMLElement | null>—
velocityFactor
How strongly scroll speed boosts the rows. 0 turns the effect off.
number1
separator
Shown between repeats of a row.
React.ReactNode"·"
rowClassName
Classes for every row, e.g. the font size and weight.
string—

Also accepts every <div> attribute.

ScrollVelocityRow

PropTypeDefault
children*
Content of one repeat; it is copied as many times as needed to fill the row.
React.ReactNode—
baseVelocity
Pixels per second at rest. Negative moves to the right.
number40
scrollContainer
Scrollable element to track. Defaults to the window.
React.RefObject<HTMLElement | null>—
velocityFactor
How strongly scroll speed boosts the row. 0 turns the effect off.
number1

Also accepts every <div> attribute.

Accessibility

  • ScrollVelocity puts all items once in an sr-only paragraph and marks the moving rows aria-hidden.
  • ScrollVelocityRow alone marks only the extra copies aria-hidden; the first copy stays readable, so give images alt text.
  • Reduced motion keeps rows still, so the text can be read.
  • With scrollContainer, make that container keyboard-scrollable: tabIndex={0}, an aria-label (or role="region" with a name) and a visible focus ring.

Motion

  • Each row drifts at `baseVelocity` px per second (40); ScrollVelocity alternates direction per row. Positive values move left.
  • Scroll speed is smoothed with a spring (damping 50, stiffness 400) and adds 5x the base speed per 1000 px/s of scroll, times `velocityFactor`; scrolling up flips the direction.
  • Content is repeated enough times to cover the row plus one copy, and wraps seamlessly.
  • Rows stop while off-screen. Reduced motion: rows do not move at all.
  • Moves one motion value per row on requestAnimationFrame (frame delta capped at 50 ms), so React does not re-render while it runs. A ResizeObserver recomputes the copy count.

Notes for AI

Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.

  • ScrollVelocity takes items (string[]), one row each; ScrollVelocityRow takes children.
  • baseVelocity is px per second; negative values move to the right.
  • Style the text size with rowClassName (ScrollVelocity) or className (ScrollVelocityRow).
  • It tracks the window scroll by default; pass scrollContainer (a ref) when the page scrolls inside a div.

FAQ

Marquee or ScrollVelocity?

Marquee is a steady CSS ticker that pauses on hover. ScrollVelocity is driven by JavaScript and reacts to scroll speed and direction.

Can it track a scrolling div instead of the window?

Yes, pass scrollContainer with a ref to that element.

Related