Skip to content

AnimateMarquee

Content scrolling steadily past, forever. The content is laid down twice and each copy travels exactly its own length, so there is no seam and no frame where the strip is empty.

tsx
import { AnimateMarquee } from 'neba';

<AnimateMarquee speed={45} gap="1.5rem">
  {customers.map((name) => (
    <Chip key={name}>{name}</Chip>
  ))}
</AnimateMarquee>;

Props

PropTypeDefaultDescription
orientationshared'horizontal' | 'vertical''horizontal'Which way the strip runs
reversebooleanfalseRuns it the other way: left to right, or bottom to top
speednumber60How fast the content travels, in pixels per second. A speed rather than a duration, so a short strip and a long one move at the same pace
gapnumber | string'2rem'The gap between items, and between the last item and the first of the next pass
copiesnumber2How many copies are laid end to end. Raise it only when the content is short enough to leave a hole behind itself
pauseOnHoverbooleantrueStops while the pointer is on it or the focus is inside it, because content moving past a pointer cannot be clicked
durationsharednumberHow long one run takes, in milliseconds
delaysharednumber0How long before it starts, in milliseconds
easingstringThe easing curve, as CSS writes it. Defaults to the house curve
repeatsharednumber | 'infinite''infinite'How many times it runs
alternatesharedbooleanfalseRuns every other pass backwards, so a repeat returns instead of jumping
triggershared'mount' | 'visible' | 'hover' | 'manual''mount'What starts it. visible is on scrolling into view, hover is under the pointer (focus counts), manual is whatever play says
playbooleanRuns it when trigger is manual. Each false → true starts it over
oncesharedbooleantrueWith trigger="visible", whether it runs only the first time. Off, it runs again on every return
thresholdsharednumber0.2With trigger="visible", how much of the element has to be on screen, from 0 to 1
pausedsharedbooleanfalseHolds the animation where it is
childrenReactNodeThe things that scroll past

Every other <div> attribute passes through to the root. The settings shared by every Animate* are defined in prop conventions.

Examples

speed and reverse

Pixels per second, measured against the strip's own width, so four logos and forty move at the same pace instead of the long one becoming a blur. duration is still accepted and overrides the measurement. reverse runs it the other way.

orientation

vertical runs the strip down the box instead of across it, for a log or an activity feed. The box needs a height for it to have anywhere to run.

pauseOnHover

On by default, and not decoration: content moving past a pointer cannot be clicked reliably, and a link inside a strip that never stops is a link nobody can follow. The strip answers the focus as well as the pointer, so a link reached by tabbing to it stops travelling too.

copies and gap

gap is the space between items, and also between the last item of one pass and the first of the next. copies is how many times the content is laid end to end: two is enough for anything at least as wide as its container, and raising it is the fix for content short enough to leave a hole behind itself.

tsx
<AnimateMarquee copies={4} gap="3rem">
  <Chip>One short item</Chip>
</AnimateMarquee>

Accessibility

  • Only the first copy is read out; the rest carry aria-hidden, or a screen reader would announce the whole strip as many times as it was laid down.
  • A reduced-motion preference stops the strip and leaves the content in place.
  • pauseOnHover is what makes anything interactive inside it usable, and it covers both a pointer resting on the strip and the focus landing inside it. Do not turn it off for a strip with links or buttons in it.

Released under the MIT License