Marquee
A shadcn-style marquee component built with Ark UI primitives.
🍎Apple
🍌Banana
🍒Cherry
🍇Grape
🍉Watermelon
🍓Strawberry
import {
Marquee,
MarqueeContent,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
{ name: "Grape", logo: "🍇" },
{ name: "Watermelon", logo: "🍉" },
{ name: "Strawberry", logo: "🍓" },
] as const;
const MarqueeBasicExample = () => (
<div className="w-full max-w-xl">
<Marquee translations={{ root: "Scrolling fruit labels" }}>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</Marquee>
</div>
);
export default MarqueeBasicExample;
Installation
npx shadcn@latest add @ark-cn/marqueeInstall the dependency required by this primitive:
npm install @ark-ui/reactCopy the component source into your app:
TSXcomponents/ui/marquee.tsx
"use client";
import {
Marquee as MarqueePrimitive,
useMarquee,
useMarqueeContext,
} from "@ark-ui/react/marquee";
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";
export type MarqueeProps = ComponentProps<typeof MarqueePrimitive.Root>;
export type MarqueeViewportProps = ComponentProps<
typeof MarqueePrimitive.Viewport
>;
export type MarqueeContentProps = ComponentProps<
typeof MarqueePrimitive.Content
>;
export type MarqueeItemProps = ComponentProps<typeof MarqueePrimitive.Item>;
export type MarqueeEdgeProps = ComponentProps<typeof MarqueePrimitive.Edge>;
export type MarqueeRootProviderProps = ComponentProps<
typeof MarqueePrimitive.RootProvider
>;
export const Marquee = ({ className, ...props }: MarqueeProps) => (
<MarqueePrimitive.Root
className={cn(
"w-full max-w-full text-foreground",
"data-[orientation=horizontal]:h-20 data-[orientation=vertical]:h-60",
className,
)}
data-slot="marquee-root"
{...props}
/>
);
export const MarqueeViewport = ({
className,
...props
}: MarqueeViewportProps) => (
<MarqueePrimitive.Viewport
className={cn("h-full w-full", className)}
data-slot="marquee-viewport"
{...props}
/>
);
export const MarqueeContent = ({
className,
...props
}: MarqueeContentProps) => (
<MarqueePrimitive.Content
className={cn(className)}
data-slot="marquee-content"
{...props}
/>
);
export const MarqueeItem = ({ className, ...props }: MarqueeItemProps) => (
<MarqueePrimitive.Item
className={cn(
"flex shrink-0 items-center justify-center gap-3 whitespace-nowrap rounded-md border border-border px-6 py-4 text-sm select-none",
className,
)}
data-slot="marquee-item"
{...props}
/>
);
export const MarqueeEdge = ({ className, ...props }: MarqueeEdgeProps) => (
<MarqueePrimitive.Edge
className={cn("data-[orientation=horizontal]:w-[20%]", className)}
data-slot="marquee-edge"
{...props}
/>
);
export const MarqueeRootProvider = ({
className,
...props
}: MarqueeRootProviderProps) => (
<MarqueePrimitive.RootProvider
className={cn(
"w-full max-w-full text-foreground",
"data-[orientation=horizontal]:h-20 data-[orientation=vertical]:h-60",
className,
)}
data-slot="marquee-root"
{...props}
/>
);
export const MarqueeContext = MarqueePrimitive.Context;
export type {
MarqueePauseStatusDetails,
MarqueeSide,
} from "@ark-ui/react/marquee";
export { useMarquee, useMarqueeContext };
Add the following CSS to your stylesheet (e.g. styles.css):
@keyframes marquee-x {
from { transform: translateX(0); }
to { transform: translateX(var(--marquee-translate)); }
}
@keyframes marquee-y {
from { transform: translateY(0); }
to { transform: translateY(var(--marquee-translate)); }
}
[data-slot="marquee-root"][data-paused] *,
[data-slot="marquee-root"][data-paused] {
animation-play-state: paused !important;
}
[data-slot="marquee-content"] {
animation-timing-function: linear;
animation-duration: var(--marquee-duration);
animation-delay: var(--marquee-delay);
animation-iteration-count: var(--marquee-loop-count);
}
[data-slot="marquee-content"][data-side="start"],
[data-slot="marquee-content"][data-side="end"] {
animation-name: marquee-x;
}
[data-slot="marquee-content"][data-side="top"],
[data-slot="marquee-content"][data-side="bottom"] {
animation-name: marquee-y;
}
[data-slot="marquee-content"][data-reverse] {
animation-direction: reverse;
}
@media (prefers-reduced-motion: reduce) {
[data-slot="marquee-content"] {
animation: none !important;
}
}
[data-slot="marquee-edge"] {
z-index: 10;
}
[data-slot="marquee-edge"][data-orientation="horizontal"][data-side="start"] {
background: linear-gradient(to right, var(--background), transparent);
}
[data-slot="marquee-edge"][data-orientation="horizontal"][data-side="end"] {
background: linear-gradient(to left, var(--background), transparent);
}
[data-slot="marquee-edge"][data-orientation="vertical"][data-side="top"] {
height: 20%;
background: linear-gradient(to bottom, var(--background), transparent);
}
[data-slot="marquee-edge"][data-orientation="vertical"][data-side="bottom"] {
height: 20%;
background: linear-gradient(to top, var(--background), transparent);
}Update import aliases to match your project setup.
Usage
import * as Marquee from "@/components/ui/marquee"Read exported parts in src/components/ui/marquee.tsx and compose the primitive according to the Ark UI pattern for this component.
Examples
Basic
🍎Apple
🍌Banana
🍒Cherry
🍇Grape
🍉Watermelon
🍓Strawberry
import {
Marquee,
MarqueeContent,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
{ name: "Grape", logo: "🍇" },
{ name: "Watermelon", logo: "🍉" },
{ name: "Strawberry", logo: "🍓" },
] as const;
const MarqueeBasicExample = () => (
<div className="w-full max-w-xl">
<Marquee translations={{ root: "Scrolling fruit labels" }}>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</Marquee>
</div>
);
export default MarqueeBasicExample;
Auto Fill
🍎Apple
🍌Banana
🍒Cherry
import {
Marquee,
MarqueeContent,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS_SHORT = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
] as const;
const MarqueeAutoFillExample = () => (
<div className="w-full max-w-xl">
<Marquee autoFill spacing="2rem" translations={{ root: "Auto-filled row" }}>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS_SHORT.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</Marquee>
</div>
);
export default MarqueeAutoFillExample;
Context
Paused: false · Orientation: horizontal
🍎Apple
🍌Banana
🍒Cherry
import {
Marquee,
MarqueeContent,
MarqueeContext,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS_SHORT = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
] as const;
const MarqueeContextExample = () => (
<div className="w-full max-w-xl">
<Marquee pauseOnInteraction translations={{ root: "Context API" }}>
<MarqueeContext>
{(api) => (
<>
<p className="mb-2 text-muted-foreground text-xs">
Paused: {String(api.paused)} · Orientation: {api.orientation}
</p>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS_SHORT.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</>
)}
</MarqueeContext>
</Marquee>
</div>
);
export default MarqueeContextExample;
Reverse
🍎Apple
🍌Banana
🍒Cherry
🍇Grape
🍉Watermelon
🍓Strawberry
import {
Marquee,
MarqueeContent,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
{ name: "Grape", logo: "🍇" },
{ name: "Watermelon", logo: "🍉" },
{ name: "Strawberry", logo: "🍓" },
] as const;
const MarqueeReverseExample = () => (
<div className="w-full max-w-xl">
<Marquee reverse translations={{ root: "Reversed direction" }}>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</Marquee>
</div>
);
export default MarqueeReverseExample;
Pause on Interaction
🍎Apple
🍌Banana
🍒Cherry
🍇Grape
🍉Watermelon
🍓Strawberry
import {
Marquee,
MarqueeContent,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
{ name: "Grape", logo: "🍇" },
{ name: "Watermelon", logo: "🍉" },
{ name: "Strawberry", logo: "🍓" },
] as const;
const MarqueePauseOnInteractionExample = () => (
<div className="w-full max-w-xl">
<Marquee
pauseOnInteraction
translations={{ root: "Pause on hover or focus" }}
>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</Marquee>
</div>
);
export default MarqueePauseOnInteractionExample;
Programmatic Controls
🍎Apple
🍌Banana
🍒Cherry
🍇Grape
🍉Watermelon
🍓Strawberry
import { Button } from "@/components/ui/button";
import {
MarqueeContent,
MarqueeItem,
MarqueeRootProvider,
MarqueeViewport,
useMarquee,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
{ name: "Grape", logo: "🍇" },
{ name: "Watermelon", logo: "🍉" },
{ name: "Strawberry", logo: "🍓" },
] as const;
const MarqueeProgrammaticExample = () => {
const marquee = useMarquee();
return (
<div className="flex w-full max-w-xl flex-col gap-3">
<MarqueeRootProvider value={marquee}>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</MarqueeRootProvider>
<div className="flex flex-wrap gap-2">
<Button
onClick={() => marquee.pause()}
size="sm"
type="button"
variant="outline"
>
Pause
</Button>
<Button
onClick={() => marquee.resume()}
size="sm"
type="button"
variant="outline"
>
Resume
</Button>
<Button
onClick={() => marquee.togglePause()}
size="sm"
type="button"
variant="outline"
>
Toggle
</Button>
<Button
onClick={() => marquee.restart()}
size="sm"
type="button"
variant="outline"
>
Restart
</Button>
</div>
</div>
);
};
export default MarqueeProgrammaticExample;
Finite Loops
🍎Apple
🍌Banana
🍒Cherry
🍇Grape
🍉Watermelon
🍓Strawberry
Loops finished: 0 · Runs completed: 0
import { useState } from "react";
import {
Marquee,
MarqueeContent,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
{ name: "Grape", logo: "🍇" },
{ name: "Watermelon", logo: "🍉" },
{ name: "Strawberry", logo: "🍓" },
] as const;
const MarqueeFiniteLoopsExample = () => {
const [loopsFinished, setLoopsFinished] = useState(0);
const [runsCompleted, setRunsCompleted] = useState(0);
return (
<div className="flex w-full max-w-xl flex-col gap-3">
<Marquee
loopCount={3}
onComplete={() => setRunsCompleted((count) => count + 1)}
onLoopComplete={() => setLoopsFinished((count) => count + 1)}
translations={{ root: "Finite loop marquee" }}
>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</Marquee>
<p className="text-muted-foreground text-xs">
Loops finished: {loopsFinished} · Runs completed: {runsCompleted}
</p>
</div>
);
};
export default MarqueeFiniteLoopsExample;
With Edges
🍎Apple
🍌Banana
🍒Cherry
🍇Grape
🍉Watermelon
🍓Strawberry
import {
Marquee,
MarqueeContent,
MarqueeEdge,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
{ name: "Grape", logo: "🍇" },
{ name: "Watermelon", logo: "🍉" },
{ name: "Strawberry", logo: "🍓" },
] as const;
const MarqueeWithEdgesExample = () => (
<div className="w-full max-w-xl">
<Marquee translations={{ root: "Marquee with edge fades" }}>
<MarqueeEdge side="start" />
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
<MarqueeEdge side="end" />
</Marquee>
</div>
);
export default MarqueeWithEdgesExample;
Orientation Compare
Horizontal
🍎Apple
🍌Banana
🍒Cherry
Vertical (side bottom)
🍎Apple
🍌Banana
🍒Cherry
import {
Marquee,
MarqueeContent,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS_SHORT = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
] as const;
const MarqueeOrientationCompareExample = () => (
<div className="grid w-full max-w-3xl gap-6 sm:grid-cols-2">
<div className="flex min-w-0 flex-col gap-2">
<p className="font-medium text-foreground text-xs">Horizontal</p>
<Marquee
className="max-w-full"
translations={{ root: "Horizontal orientation" }}
>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS_SHORT.map((item, index) => (
<MarqueeItem key={`h-${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</Marquee>
</div>
<div className="flex min-w-0 flex-col gap-2">
<p className="font-medium text-foreground text-xs">
Vertical (side bottom)
</p>
<Marquee
className="max-w-full"
side="bottom"
translations={{ root: "Vertical orientation" }}
>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS_SHORT.map((item, index) => (
<MarqueeItem key={`v-${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</Marquee>
</div>
</div>
);
export default MarqueeOrientationCompareExample;
Speed
🍎Apple
🍌Banana
🍒Cherry
🍇Grape
🍉Watermelon
🍓Strawberry
import {
Marquee,
MarqueeContent,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
{ name: "Grape", logo: "🍇" },
{ name: "Watermelon", logo: "🍉" },
{ name: "Strawberry", logo: "🍓" },
] as const;
const MarqueeSpeedExample = () => (
<div className="w-full max-w-xl">
<Marquee speed={120} translations={{ root: "Faster scroll" }}>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</Marquee>
</div>
);
export default MarqueeSpeedExample;
Vertical
🍎Apple
🍌Banana
🍒Cherry
🍇Grape
🍉Watermelon
🍓Strawberry
import {
Marquee,
MarqueeContent,
MarqueeItem,
MarqueeViewport,
} from "@/components/ui/marquee";
const MARQUEE_FRUITS = [
{ name: "Apple", logo: "🍎" },
{ name: "Banana", logo: "🍌" },
{ name: "Cherry", logo: "🍒" },
{ name: "Grape", logo: "🍇" },
{ name: "Watermelon", logo: "🍉" },
{ name: "Strawberry", logo: "🍓" },
] as const;
const MarqueeVerticalExample = () => (
<div className="w-full max-w-xl">
<Marquee side="bottom" translations={{ root: "Vertical marquee" }}>
<MarqueeViewport>
<MarqueeContent>
{MARQUEE_FRUITS.map((item, index) => (
<MarqueeItem key={`${item.name}-${index}`}>
<span className="text-2xl leading-none">{item.logo}</span>
<span className="font-medium">{item.name}</span>
</MarqueeItem>
))}
</MarqueeContent>
</MarqueeViewport>
</Marquee>
</div>
);
export default MarqueeVerticalExample;
API reference
This component mirrors the upstream Ark UI primitive.
See the ARK UI documentation for the full API.