Client component
Marquee
An infinitely scrolling strip for logos and highlights: CSS-driven, pausable, gradient-masked.
React
TypeScript
Vitest
Vite
Next.js
CVA
pnpm
"use client";
import { Marquee, Text, Box } from "astralis-ui";
const stack = ["React", "TypeScript", "Vitest", "Vite", "Next.js", "CVA", "pnpm"];
export function MarqueeDemo() {
return (
<Box w="full" maxW="md">
<Marquee speed={40} gap="0.75rem" pauseOnHover gradient>
{stack.map((name) => (
<Marquee.Item key={name}>
<Box border="normal" borderColor="base" bg="subtle" px="4" py="2" rounded="full">
<Text as="span" size="sm">{name}</Text>
</Box>
</Marquee.Item>
))}
</Marquee>
</Box>
);
}React
TypeScript
Vitest
Next.js
pnpm
direction
speed
gap
gradientWidth
import { Badge, Marquee } from "astralis-ui";
<Marquee>
<Marquee.Item>
<Badge variant="surface" colorScheme="gray">React</Badge>
</Marquee.Item>
<Marquee.Item>
<Badge variant="surface" colorScheme="gray">TypeScript</Badge>
</Marquee.Item>
<Marquee.Item>
<Badge variant="surface" colorScheme="gray">Vitest</Badge>
</Marquee.Item>
<Marquee.Item>
<Badge variant="surface" colorScheme="gray">Next.js</Badge>
</Marquee.Item>
<Marquee.Item>
<Badge variant="surface" colorScheme="gray">pnpm</Badge>
</Marquee.Item>
</Marquee>
Import#
import { Marquee } from "astralis-ui";
// Marquee.ItemUsage#
Children are duplicated once (the clone is aria-hidden) for a seamless
loop; the animation is pure CSS keyframes, so it costs nothing at runtime.
gradient masks the edges into the background.
Opposing rows#
Semantic tokens
Runtime theming
Dark mode
Responsive props
Compound components
Accent channel
Zero build step
Typed styles
"use client";
import { Marquee, Text, Box, VStack } from "astralis-ui";
const row1 = ["Semantic tokens", "Runtime theming", "Dark mode", "Responsive props"];
const row2 = ["Compound components", "Accent channel", "Zero build step", "Typed styles"];
export function MarqueeReverse() {
return (
/* Two rows in opposite directions: the classic logo-wall move. */
<VStack gap="3" w="full" maxW="md">
<Marquee speed={35} gap="0.75rem" gradient>
{row1.map((label) => (
<Marquee.Item key={label}>
<Box bg="brand-subtle" px="4" py="1.5" rounded="full">
<Text as="span" size="xs">{label}</Text>
</Box>
</Marquee.Item>
))}
</Marquee>
<Marquee speed={35} gap="0.75rem" gradient reverse>
{row2.map((label) => (
<Marquee.Item key={label}>
<Box bg="purple-subtle" px="4" py="1.5" rounded="full">
<Text as="span" size="xs">{label}</Text>
</Box>
</Marquee.Item>
))}
</Marquee>
</VStack>
);
}Props#
| Prop | Type | Default | Description |
|---|---|---|---|
direction | "left" | "right" | "up" | "down" | "left" | Scroll axis and direction. |
speed | number | 50 | Pixels per second. |
gap | string | "1rem" | CSS length between items, and at the loop seam. |
reverse | boolean | false | Flips the direction. |
pauseOnHover | boolean | false | Pauses while the pointer is over the strip. |
pauseOnFocus | boolean | false | Pauses while focus is inside. Turn it on when items are interactive. |
gradient | boolean | false | Fades both edges into the background. |
gradientColor | string | page background | CSS color the edges fade into. |
gradientWidth | string | "10%" | Width of each edge fade. |
loopCount | number | 0 | Loops before the strip stops. 0 loops forever. |
Parts#
| Prop | Type | Default | Description |
|---|---|---|---|
Marquee.Item | div props | None | One entry in the strip. The items are rendered twice for a seamless loop, and the copy is hidden from assistive technology. |
Accessibility#
The duplicated track is aria-hidden, and pauseOnFocus lets keyboard
users freeze the motion. Turn it on when items are interactive.