LayoutSimpleApril 8, 2026
Infinite Ticker
An infinitely scrolling horizontal text ticker that loops seamlessly. Items are duplicated and a CSS animation shifts the track by exactly 50%, creating a gapless loop without JavaScript.
View Full Demo →Preview
Brand IdentityWeb DesignMotionShopifyArt DirectionNo-5 StudioBrand IdentityWeb DesignMotionShopifyArt DirectionNo-5 Studio
Source
index.jsx
import styles from "./styles.module.css";
export default function InfiniteTicker({ items = [], speed = 30 }) {
// Duplicate items to ensure seamless loop
const doubled = [...items, ...items];
return (
<div className={styles.wrapper}>
<div
className={styles.track}
style={{ "--ticker-duration": `${speed}s` }}
>
{doubled.map((item, i) => (
<span key={i} className={styles.item}>
<span className={styles.text}>{item}</span>
<span className={styles.dot} aria-hidden="true">·</span>
</span>
))}
</div>
</div>
);
}
styles.module.css
.wrapper {
overflow: hidden;
border-top: 1px solid #e5e5e5;
border-bottom: 1px solid #e5e5e5;
padding: 0.875rem 0;
background: #fafafa;
}
.track {
display: flex;
width: max-content;
animation: ticker var(--ticker-duration, 30s) linear infinite;
}
.item {
display: flex;
align-items: center;
flex-shrink: 0;
white-space: nowrap;
}
.text {
font-size: 0.9375rem;
font-weight: 500;
letter-spacing: -0.01em;
color: #1a1a1a;
padding: 0 0.5rem;
}
.dot {
color: #9b9b9b;
padding: 0 0.25rem;
}
@keyframes ticker {
from {
transform: translateX(0);
}
to {
/* Move exactly half (one set of items) for seamless loop */
transform: translateX(-50%);
}
}