- Accordion
- Alert
- Alert Dialog
- Avatar
- Badge
- Button
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Circular Progress
- Collapsible
- Compare Slider
- Dialog
- Dropdown Menu
- Gauge
- Input
- Label
- Marquee
- Navigation Menu
- Number Field
- Pagination
- Popover
- Progress
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Switch
- Table
- Tabs
- Textarea
- Toggle
- Tooltip
Demos
A row of items scrolling left, with faded edges.
"use client"
import {
Marquee,
MarqueeContent,
MarqueeEdge,
MarqueeItem,
} from "@workspace/ui/components/marquee"
const markers = [
"Vitamin D",
"Ferritin",
"HbA1c",
"LDL",
"HDL",
"hs-CRP",
"TSH",
"Testosterone",
"Cortisol",
"B12",
]
export function MarqueeDemo() {
return (
<Marquee className="w-full max-w-lg" aria-label="Biomarkers we test">
<MarqueeContent>
{markers.map((marker) => (
<MarqueeItem
key={marker}
className="rounded-full border px-4 py-2 text-sm font-medium"
>
{marker}
</MarqueeItem>
))}
</MarqueeContent>
<MarqueeEdge side="left" />
<MarqueeEdge side="right" />
</Marquee>
)
}
Usage
Use a marquee for content that is nice to glance at but not needed to complete a task: partner logos, member reviews, the list of biomarkers we test. Never put the only copy of important information or controls in a marquee, because it moves on its own and is hard to read and click.
Import it from @workspace/ui/components/marquee.
Anatomy
- Marquee — The root. Sets the direction, speed and gap, and clips the moving content.
- MarqueeContent — The moving track. It is duplicated so the loop has no gap.
- MarqueeItem — One item in the track.
- MarqueeEdge — A gradient that fades the content into the background at one side.
Notes
- Give the root an
aria-label. The duplicate copies of the content are hidden from screen readers. speedis in pixels per second, so the pace stays the same whatever the content length.- Set
autoFillwhen the content is shorter than the marquee, so it repeats enough times to fill it. - For a vertical marquee (
side="top"orside="bottom"), give the root or its parent a height. - The animation stops when the reader prefers reduced motion.
- Turn on
pauseOnHoverorpauseOnKeyboardwhenever items are worth reading, so people can stop the motion.
Examples
Vertical
Set side="top" or side="bottom" to scroll up or down.
"use client"
import {
Marquee,
MarqueeContent,
MarqueeEdge,
MarqueeItem,
} from "@workspace/ui/components/marquee"
const updates = [
"Results ready: Vitamin D",
"New report: Heart health",
"Reminder: Retest in 3 months",
"Results ready: Thyroid panel",
"New report: Metabolic health",
]
export function MarqueeVertical() {
return (
<div className="h-64 w-full max-w-xs">
<Marquee side="top" speed={30} aria-label="Recent updates">
<MarqueeContent>
{updates.map((update) => (
<MarqueeItem
key={update}
className="rounded-lg border px-4 py-3 text-sm"
>
{update}
</MarqueeItem>
))}
</MarqueeContent>
<MarqueeEdge side="top" />
<MarqueeEdge side="bottom" />
</Marquee>
</div>
)
}
Pause on hover
Set pauseOnHover to stop while the pointer is over the marquee, and pauseOnKeyboard to let people focus it and press Space to pause and resume.
"use client"
import {
Marquee,
MarqueeContent,
MarqueeEdge,
MarqueeItem,
} from "@workspace/ui/components/marquee"
const reviews = [
{ name: "Amira", quote: "Finally understood my results." },
{ name: "Jonas", quote: "The retest reminders keep me on track." },
{ name: "Priya", quote: "Clear, calm and quick." },
{ name: "Leo", quote: "My doctor loved the report." },
]
export function MarqueePause() {
return (
<Marquee
pauseOnHover
pauseOnKeyboard
speed={40}
className="w-full max-w-lg"
aria-label="Member reviews"
>
<MarqueeContent>
{reviews.map((review) => (
<MarqueeItem
key={review.name}
className="flex w-64 flex-col gap-1 rounded-lg border p-4"
>
<span className="text-sm">“{review.quote}”</span>
<span className="text-xs text-muted-foreground">
{review.name}
</span>
</MarqueeItem>
))}
</MarqueeContent>
<MarqueeEdge side="left" size="sm" />
<MarqueeEdge side="right" size="sm" />
</Marquee>
)
}
Reverse
Set reverse to run the other way. Rows moving in opposite directions work well together.
"use client"
import {
Marquee,
MarqueeContent,
MarqueeEdge,
MarqueeItem,
} from "@workspace/ui/components/marquee"
const rowOne = ["Sleep", "Energy", "Focus", "Recovery", "Mood"]
const rowTwo = ["Heart", "Thyroid", "Liver", "Kidney", "Hormones"]
function Row({ items, reverse }: { items: string[]; reverse?: boolean }) {
return (
<Marquee reverse={reverse} autoFill speed={35}>
<MarqueeContent>
{items.map((item) => (
<MarqueeItem
key={item}
className="rounded-full bg-secondary px-4 py-1.5 text-sm text-secondary-foreground"
>
{item}
</MarqueeItem>
))}
</MarqueeContent>
<MarqueeEdge side="left" size="sm" />
<MarqueeEdge side="right" size="sm" />
</Marquee>
)
}
export function MarqueeReverse() {
return (
<div className="flex w-full max-w-lg flex-col gap-3">
<Row items={rowOne} />
<Row items={rowTwo} reverse />
</div>
)
}
Props
These are the props you set most often. Unlisted props pass through to the underlying element.
Marquee
| Prop | Type | Default | Description |
|---|---|---|---|
side | "left", "right", "top" or "bottom" | "left" | The direction the content moves toward. |
speed | number | 50 | Speed in pixels per second. |
gap | string | "1rem" | Space between items and between copies, as a CSS length. |
delay | number | 0 | Seconds to wait before starting. |
loopCount | number | 0 | Number of loops. 0 loops forever. |
autoFill | boolean | false | Repeat the content to fill the marquee. |
pauseOnHover | boolean | false | Pause while the pointer is over it. |
pauseOnKeyboard | boolean | false | Make it focusable and toggle pause with Space. |
reverse | boolean | false | Run the animation the other way. |
dir | "ltr" or "rtl" | Inherited | Text direction. Defaults to the nearest direction provider. |
render | ReactElement or function | — | Replaces the root div with your own element. |
className | string | — | Classes merged onto the root. |
MarqueeEdge
| Prop | Type | Default | Description |
|---|---|---|---|
side | "left", "right", "top" or "bottom" | — | The side to fade. |
size | "sm", "default" or "lg" | "default" | Width (or height) of the fade: a sixth, a quarter or a third. |