Infinite Marquee

Two rows of tags glide in opposite directions without a seam, and pause while you hover them.

  • CSS
  • Layout
  • Loop
  • Added
  • Branding
  • Web design
  • Motion
  • Next.js
  • Copywriting
  • SEO
  • E-commerce
  • Dashboards
  • Prototypes
  • Landing pages
  • Design systems

AI prompt

Build a seamless CSS marquee in React with two rows moving in opposite directions. In each row, render the item list twice inside one flex track (the second copy aria-hidden) and animate the track from translateX(0) to translateX(-50%) with a linear infinite animation (30s for row one, 36s reversed for row two), so the loop has no jump. Use gap on the track and padding-right equal to the gap so the halves line up exactly. Fade the edges with mask-image: linear-gradient(90deg, transparent, black 10%, black 90%, transparent). Pause the animation on hover and focus-within with animation-play-state: paused. Items are rounded tags. Under prefers-reduced-motion, stop the animation and let the row scroll horizontally instead.

Source

Demo.tsx
'use client'

import s from './demo.module.css'

function Row({ items, reverse }: { items: string[]; reverse?: boolean }) {
  return (
    <div className={s.row}>
      <div className={reverse ? `${s.track} ${s.reverse}` : s.track}>
        {[0, 1].map((copy) => (
          <ul key={copy} className={s.list} aria-hidden={copy === 1 || undefined}>
            {items.map((t) => (
              <li key={t} className={s.tag}>
                {t}
              </li>
            ))}
          </ul>
        ))}
      </div>
    </div>
  )
}

export default function Marquee({ labels }: { labels: Record<string, string> }) {
  return (
    <div className={s.wrap}>
      <Row items={labels.row1.split('|')} />
      <Row items={labels.row2.split('|')} reverse />
    </div>
  )
}
demo.module.css
.wrap { display: grid; gap: 12px; width: 100%; }

.row {
  overflow: hidden;
  mask-image: linear-gradient(90deg, transparent, black 10%, black 90%, transparent);
}

.track {
  display: flex;
  width: max-content;
  animation: slide 30s linear infinite;
}

.reverse { animation-duration: 36s; animation-direction: reverse; }
.row:hover .track, .row:focus-within .track { animation-play-state: paused; }

.list {
  display: flex;
  gap: 10px;
  margin: 0;
  padding: 0 10px 0 0;
  list-style: none;
}

.tag {
  padding: 8px 14px;
  border: 1px solid var(--stage-rule);
  border-radius: 999px;
  background: var(--stage-surface);
  color: var(--stage-ink);
  font: 500 var(--text-sm) / 1 var(--font-body);
  white-space: nowrap;
}

.reverse .tag:nth-child(odd) { border-color: var(--stage-accent); color: var(--stage-accent); }

@keyframes slide { to { transform: translateX(-50%); } }

@media (prefers-reduced-motion: reduce) {
  .track { animation: none; }
  .row { overflow-x: auto; }
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.