WebCraft

translateX(-50%)

A marquee that never jumps

FigmaAstroVitePlaywright

CaveatThe track holds the list twice and travels exactly -50%. Any other distance, or a single copy, and the loop visibly snaps back at the seam.

FallbackMark the second copy aria-hidden — it is decoration, and a screen reader should not read the same list twice. Stop the animation under prefers-reduced-motion.

CSS
.track {
  display: flex;
  width: max-content;  /* the list, twice */
  animation: slide 14s linear infinite;
}
@keyframes slide {
  to { transform: translateX(-50%); }
}

Published

Questions

<details name>

Why does my infinite marquee jump at the end of each loop?

The travel and the content have to agree. Hold the list twice in the track and animate exactly -50%: at that point the second copy sits precisely where the first started, so resetting to 0 is invisible. Any other distance, or a single copy, and the seam shows.

Why is the scroll uneven?

A default timing function eases in and out of every iteration, which reads as a stutter on a continuous loop. linear is the only correct choice here.

How should a marquee behave for screen readers and reduced motion?

Mark the duplicate aria-hidden — it is decoration, and nobody should hear the same list twice. Then stop the animation under prefers-reduced-motion: a permanent horizontal crawl is exactly what that preference is asking you not to do.