# Velocity marquee (goodtabs UI)

A marquee whose speed and direction follow scroll velocity, smoothed per frame.

## Install

```bash
npx shadcn@latest add https://ui.goodtabs.co/r/velocity-marquee.json
```

Or, with the @goodtabs namespace configured in components.json: `npx shadcn@latest add @goodtabs/velocity-marquee`

## Usage

```tsx
import { VelocityMarquee } from "@/components/ui/velocity-marquee"

<VelocityMarquee aria-label="Details we sweat" className="text-7xl font-medium">
  <span className="whitespace-nowrap">Hairlines</span>
  <span className="whitespace-nowrap">Focus rings</span>
</VelocityMarquee>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| speed | `number` | `48` | Resting drift in pixels per second. |
| direction | `1 | -1` | `1` | 1 drifts left on the way down the page, -1 drifts right. Scrolling up reverses either. |
| gain | `number` | `0.35` | Extra pixels per second of drift for each pixel per second of scroll. |
| smoothing | `number` | `6` | Rate k in 1 - e^(-k dt). Higher follows the scroll more tightly. |
| gap | `number` | `48` | Space between items in pixels, repeated at the seam. |

## When to use

- A big type band between two sections of a landing page
- A row of customer names or tools, where order does not matter
- A page with enough scroll for the velocity to be felt

## When not to use

- Content people have to read in full
- Short pages with nothing to scroll
- Inside a nested scroller: it reads the window's scroll

## Notes for agents

- GSAP installs from npm under GSAP's own licence. No GSAP code ships in this registry
- Give it an aria-label. It is a focusable group, so a keyboard user can stop the drift. Clones are aria-hidden and inert
- Hover and focus stop the resting drift. Scrolling still moves it, because that motion is the person's own and a full stop would freeze it under a resting pointer
- Under reduced motion the clones go and the first set wraps as a centred static row, so nothing is cut off
- Scroll velocity comes from window.scrollY each frame, so it needs the window as the scroller. The loop stops while the row is off screen

## The maths

- Velocity eases by v += (target - v) × (1 - e^(-k dt)), so k = 6 covers 63 percent of the gap in 167ms at any refresh rate.
- Target is heading × speed + scroll × gain, so 2000px/s of scroll adds 700px/s at the default gain.
- Copies are ceil(W / set) + 1 and x wraps by one set's width, so the seam never shows.

Docs: https://ui.goodtabs.co/components/velocity-marquee