Marquee announcement bar
Infinite scrolling strip of short messages that pauses on hover.
componentmarqueeanypromo
$
npx shadcn@latest add @storekit/announcement-marqueeannouncement-marquee
Live and interactive. Switch the brand in the frame header to see the same source under another token file.
Installation
$
npx shadcn@latest add @storekit/announcement-marquee$
pnpm dlx shadcn@latest add @storekit/announcement-marquee$
yarn dlx shadcn@latest add @storekit/announcement-marquee$
bunx --bun shadcn@latest add @storekit/announcement-marqueeSource
components/storekit/announcement-bar/announcement-marquee.tsx
45 lines
import { cn } from "@/components/storekit/lib/cn"
/* Infinite announcement strip. The track holds the message list twice and
slides by -50%, so the loop is seamless. Hover or focus pauses it; reduced
motion stops it and lets the row wrap. Screen readers get the list once. */
export function AnnouncementMarquee({
messages = ["Free shipping over $50", "Third-party tested, every batch", "Subscribe and save 15%", "30-day guarantee"],
duration = 28,
className,
}: {
messages?: string[]
/** Seconds for one full loop. */
duration?: number
className?: string
}) {
const Row = ({ hidden }: { hidden?: boolean }) => (
<ul aria-hidden={hidden || undefined} className="flex shrink-0 items-center">
{messages.map((m, i) => (
<li key={i} className="flex items-center whitespace-nowrap">
<span className="px-6">{m}</span>
<span aria-hidden className="h-1 w-1 rotate-45 bg-sk-bar-ink/50" />
</li>
))}
</ul>
)
return (
<div
role="region"
aria-label="Announcements"
data-sk-section="announcement-bar"
data-sk-variant="announcement-marquee"
className={cn("group relative overflow-hidden bg-sk-bar text-sk-bar-ink", className)}
>
<div
className="flex h-10 w-max items-center font-sk-mono text-[11px] uppercase tracking-[0.14em] animate-sk-marquee group-hover:[animation-play-state:paused] group-focus-within:[animation-play-state:paused] motion-reduce:animate-none"
style={{ animationDuration: `${duration}s` }}
>
<Row />
<Row hidden />
</div>
</div>
)
}
Usage
Needs the sk-marquee keyframe from the StoreKit Tailwind preset.