Banner
An auto-scrolling marquee banner with seamless looping, hover pause and direction control
Installation
Usage
Basic Usage
A data-driven announcement banner. Items repeat automatically to fill the width and loop seamlessly; hovering pauses the scroll.
"use client";
import { Banner } from "@/ui";
const messages = [
{
key: "release",
content: "🎉 Litefy v2.0 is out — new Context Menu, Wizard and Banner components",
},
{
key: "docs",
content: "📚 New composition guides: Combobox, Multi Select, Date Picker",
},
{
key: "theme",
content: "🎨 12 brand themes with automatic surface tuning",
},
];
export default function BannerBasicDemo() {
return (
<div className="w-full max-w-md overflow-hidden rounded-md">
<Banner
items={messages}
speed={20}
classNames={{ item: "gap-2 px-8 py-2 text-sm whitespace-nowrap" }}
/>
</div>
);
}
Custom
Assemble the parts manually for full control: a custom-styled viewport, your own track speed and direction, and duplicated copies for the seamless loop (the extra copy is aria-hidden). In parts mode you render the two copies yourself — the track shifts by exactly -50%, so both halves must be identical.
"use client";
import { Megaphone, Sparkles, Zap } from "lucide-react";
import { BannerItem, BannerTrack, BannerViewport } from "@/ui";
const messages = [
{ icon: Megaphone, text: "Server maintenance Sunday 02:00 UTC" },
{ icon: Sparkles, text: "Banner component just landed" },
{ icon: Zap, text: "Anchor positioning powers every popover" },
];
export default function BannerCustomDemo() {
return (
<div className="w-full max-w-md rounded-md">
<BannerViewport
className="py-2 bg-primary text-primary-foreground"
aria-label="Announcements"
>
<BannerTrack duration={12} direction="right" playing>
{[0, 1].map((copy) => (
<div
key={copy}
aria-hidden={copy > 0 || undefined}
className="flex shrink-0 items-center"
>
{messages.map(({ icon: Icon, text }) => (
<BannerItem key={text} className="gap-2 px-6 text-sm">
<Icon className="size-3.5 text-primary" />
<span className="whitespace-nowrap">{text}</span>
</BannerItem>
))}
</div>
))}
</BannerTrack>
</BannerViewport>
</div>
);
}
API Reference
High-level Components
Banner
A data-driven marquee banner. It measures the viewport, repeats the item set enough times to fill twice the width, and animates the track with the Web Animations API — no global keyframes required. Content copies beyond the first are aria-hidden.
| Prop | Type | Default | Description |
|---|---|---|---|
items | BannerItem[] | - | The scrolling content; each item renders inside a BannerItem |
speed | number | 30 | Seconds per full loop (one -50% track shift) |
direction | "left" | "right" | left | Scroll direction |
pauseOnHover | boolean | true | Pause the animation while the pointer is over the banner |
className | ClassNameValue | - | Custom classes for the viewport strip |
style | React.CSSProperties | - | Inline styles for the viewport strip |
classNames | { track?; item? } — each ClassNameValue | - | Custom classes for the track and items |
styles | { track?; item? } — each React.CSSProperties | - | Inline styles for the track and items |
...props | React.ComponentProps<"div"> | - | Native div props spread on the viewport (except children); your own onMouseEnter / onMouseLeave compose with the hover pause |
BannerItem
A single scrolling entry.
| Field | Type | Description |
|---|---|---|
key | string | number | Optional React key |
content | React.ReactNode | The rendered content |
Statics
| Static | Type | Description |
|---|---|---|
Banner.Viewport | BannerViewport | The clipping container part |
Banner.Track | BannerTrack | The animated strip part |
Banner.Item | BannerItem | The single entry part |
Composable Components
BannerViewport
The clipping container. Renders a full-width overflow-hidden div with aria-roledescription="marquee".
| Prop | Type | Description |
|---|---|---|
className | ClassNameValue | Custom CSS class |
...props | React.ComponentProps<"div"> | Supports all native div props |
BannerTrack
The animated strip. Runs a linear infinite translateX(0 → -50%) (reversed for direction="right") via the Web Animations API, and pauses automatically under prefers-reduced-motion: reduce. For a seamless loop the track must contain two identical halves — the -50% shift is what makes the loop seamless.
| Prop | Type | Default | Description |
|---|---|---|---|
duration | number | 30 | Seconds per full loop |
direction | "left" | "right" | left | Scroll direction |
playing | boolean | true | Plays or pauses the animation |
className | ClassNameValue | - | Custom CSS class (flex w-max built in) |
...props | React.ComponentProps<"div"> | - | Supports all native div props |
BannerItem
A single entry cell (flex shrink-0 items-center built in).
| Prop | Type | Description |
|---|---|---|
className | ClassNameValue | Custom CSS class |
...props | React.ComponentProps<"div"> | Supports all native div props |