More Animations Snippets
Marquee Ticker — Free HTML CSS Infinite Scroll Snippet
Marquee / Ticker · Animations · Plain HTML & CSS · Live preview
What's included
Features
About this UI Snippet
Marquee Ticker — CSS Infinite Scroll, Mask Edge Fade & Reverse Variant

A marquee is a horizontally scrolling strip of text or logos that loops continuously. It is used on landing pages to show client logos, tech stack icons, feature names, or announcement text in a space-efficient format. The CSS approach is far lighter than JavaScript-based solutions and requires no library.
How the infinite scroll works
The content sits inside a .track div that has display: flex; width: max-content. The max-content width means the track is exactly as wide as all its children combined — no wrapping. A @keyframes scroll animation runs transform: translateX(0) to translateX(-50%) continuously. The content is duplicated in the HTML so that when the animation reaches -50% it has moved exactly one copy of the content — the duplicated copy fills in seamlessly, creating an infinite loop with no jump.
The CSS mask edge fade
.marquee-wrap { -webkit-mask: linear-gradient(90deg, transparent, black 10%, black 90%, transparent); mask: linear-gradient(90deg, transparent, black 10%, black 90%, transparent) } applies a mask that fades the track to transparency at both edges. The mask is black in the middle (fully visible) and transparent at 0% and 100% (fully invisible). This creates the appearance that the content appears and disappears at the edges rather than hard-clipping.
The reverse variant
Adding .reverse to the marquee wrapper applies animation-direction: reverse. This reverses the same keyframe — the track scrolls right instead of left. Using two rows with opposite directions creates the staggered counter-scroll effect seen on marketing pages.
Pause on hover
Add .track:hover { animation-play-state: paused; } to pause the animation when the user mouses over — useful when the items are clickable.
Changing the speed
Update 18s in animation: scroll 18s linear infinite to control speed. Lower values are faster; higher values are slower. Both rows can have different durations for visual variety.
The duplicate content loop trick
The marquee contains the content twice: <div class="track"><span>items...</span><span>items...</span></div>. The CSS animation translates the track from 0 to -50% of its width. Since 50% equals exactly one copy of the content, when the animation resets from -50% to 0, the two copies line up perfectly — creating a seamless infinite loop with no visible jump. The key requirement: both copies must be identical.
The CSS mask-image edge fade
To fade the marquee content at both edges (the "infinite scroll" look), apply mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent) to the marquee container. This makes the content fade in from the left edge and fade out to the right edge. The mask uses percentage values so it adapts to any container width.
Pause on hover
Add animation-play-state: paused to .track on .marquee:hover to pause the scroll when the user hovers. This is important for marquees containing interactive content (links, buttons) — it prevents items from moving away before the user can click them.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You do not need to puzzle out why exactly two copies of the content are required. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain precisely why width: max-content on .track combined with an exact duplicate of every span is what makes translateX(-50%) loop without a visible jump, and why the .reverse variant only needs animation-direction: reverse rather than a second separate keyframe. The same assistant can help optimize it, for instance asking whether the hardcoded 16 duplicated span tags could instead be generated from a single JavaScript array to eliminate the risk of the two halves silently drifting out of sync when someone edits only one copy. It is also useful for extending the marquee: ask it to add a third counter-scrolling row at a different speed, make individual items pause the whole row on hover without stopping the other rows, or convert the fixed-text spans into linked, clickable items while keeping the loop seamless. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build an infinite scrolling "marquee ticker" with two rows in plain HTML and CSS only, no JavaScript required.
Requirements:
- Each row's track must contain the exact same list of items rendered twice back to back (not once), and the track's width must be set to max-content so it never wraps and is exactly as wide as all its children combined.
- Animate each track with a single CSS keyframe that transitions from translateX(0) to translateX(-50%) on an infinite linear loop, relying entirely on the fact that the second half of the track is an identical copy of the first half for the reset to be visually seamless.
- Wrap each track in a container with overflow: hidden and a CSS mask-image (with the -webkit- prefixed equivalent for compatibility) that fades the content to transparent at the left and right edges using percentage stops, rather than a hard clip.
- Provide a second row variant that scrolls in the opposite direction using only animation-direction: reverse on the same shared keyframe — do not write a second, separately-defined keyframe for the reverse direction.
- Style the two rows differently (e.g. plain text items with dividers in one row, pill-shaped badges in the other) to demonstrate the technique works regardless of the item's visual treatment.
- Ensure individual item text never wraps mid-item using white-space: nowrap, so the marquee's items always render as a single unbroken line even at narrow viewport widths.Step by step
How to Use
- 1Load the snippetClick "Marquee / Ticker" in the sidebar. The preview shows two rows — one scrolling left, one right — with the edge fade mask active.
- 2Update the content itemsIn the HTML panel, replace the span text in both the original and duplicate .track with your logos, tech names, or announcement text. Keep both copies identical.
- 3Change the scroll speedUpdate 18s in animation: scroll 18s linear infinite in the CSS panel. Duplicate each row at a different speed for visual variety.
- 4Add pause on hoverIn the CSS panel, add .track:hover { animation-play-state: paused; } so the marquee stops when users hover over a clickable item.
- 5Change the edge fade widthUpdate the 10% and 90% values in the mask gradient to control how much of the track fades at each edge.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
The content is duplicated in HTML — the .track contains two identical sets of items. The translateX(-50%) animation moves exactly one copy-width to the left. When it arrives, it is visually identical to the start position, creating a seamless loop.
mask: linear-gradient(90deg, transparent, black 10%, black 90%, transparent) applies a transparency mask. Areas where the mask is black are fully visible; areas where it is transparent are invisible. The 10% and 90% stops create a fade zone at each edge while the middle is fully visible.
Add class="marquee-wrap reverse" to the wrapper element. The .reverse .track rule applies animation-direction: reverse, which runs the same scroll keyframe in reverse — scrolling right instead of left.
Add .track:hover { animation-play-state: paused; } to the CSS. This is useful when marquee items are clickable links — hovering stops the scroll so the user can click.
Update the duration in animation: scroll 18s linear infinite. Lower numbers scroll faster; higher numbers slower. The two rows in this snippet can have different durations for visual variation.
Yes. Replace the span text with img elements inside the track spans. Set a fixed width and height on each img and use object-fit: contain for logos. Duplicate the img elements for the seamless loop.