Marquee announcement bar

Infinite scrolling strip of short messages that pauses on hover.

componentmarqueeanypromo
$npx shadcn@latest add @storekit/announcement-marquee
announcement-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-marquee

Source

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.

Marquee announcement bar, StoreKit