Marquee
A multi-band counter-flow infinite-scroll primitive for continuous animated content strips.
Single Band
The simplest usage: pass items and optionally a separator glyph. Items are duplicated internally for a seamless loop.
Counter-Flow Dual Band
Pass a bands array with per-band direction, durationSec, and separator. Opposite directions create the counter-flow pattern common in "trusted by" sections.
Marketing-Display Scale (Descenders + Calm Speed)
At marketing-display font sizes, items with descender glyphs (g, y, p, q, j) require the default line-height: var(--line-height-normal) to clear the band's overflow boundary. The default durationSec of 40 provides a calm, readable scroll at display scale — pass a lower value for body-text tickers. Scale typography via the --marquee-item-font-size / --marquee-item-font-weight slots.
Custom Render
Use renderItem to render rich nodes — logos, avatar groups, or styled cards — instead of plain text.
Installation
npx visor add marqueeThis copies two files into your project:
components/ui/marquee/marquee.tsx— the componentcomponents/ui/marquee/marquee.module.css— the styles
Usage
import { Marquee } from '@/components/ui/marquee/marquee';
// Single-band shorthand
<Marquee
items={['Acme Corp', 'Vercel', 'Figma']}
durationSec={20}
separator="·"
/>
// Multi-band counter-flow
<Marquee
bands={[
{ items: clientNames, direction: 'left', durationSec: 25, separator: '●' },
{ items: clientNamesReversed, direction: 'right', durationSec: 30, separator: '○' },
]}
pauseOnHover
gap="2.5rem"
/>Accessibility
- All tracks are wrapped in
aria-hidden="true"— the scrolling content is decorative. Add meaningful context viaaria-labelon the root element. - Pause-on-hover (
pauseOnHover, defaulttrue) lets users read content without time pressure. prefers-reduced-motion: reducedisables the animation entirely, rendering items statically.
API Reference
MarqueeProps
No props data available for “marquee”.
MarqueeBand
| Prop | Type | Default | Description |
|---|---|---|---|
items | ReactNode[] | — | Items to display in this band |
direction | "left" | "right" | "left" | Scroll direction |
durationSec | number | 40 | Duration of one full cycle in seconds. Tuned for marketing-display sizes; pass a lower value (e.g. 20) for ticker-style strips. |
separator | ReactNode | string | — | Separator rendered between items |
Customization Slots
The marquee root (.root) exposes the following CSS custom properties. Defaults resolve to the body-text style used by ticker strips — override any of them in the root style (or a parent token-override scope) to retheme without forking the component.
| Slot | Default | Purpose |
|---|---|---|
--marquee-item-color | var(--text-primary) | Item text color |
--marquee-item-font-size | var(--font-size-sm) | Item font size |
--marquee-item-font-weight | var(--font-weight-medium) | Item font weight |
--marquee-item-line-height | var(--line-height-normal) | Item line-height (also applied to separator) |
--marquee-item-letter-spacing | normal | Item letter-spacing |
--marquee-separator-color | var(--text-tertiary) | Separator color |
--marquee-separator-font-size | var(--marquee-item-font-size) | Separator font size — defaults to item size |