ComponentsText Animations
Infinite Ribbon
Continuous marquee-style scrolling ribbon for React. Pure CSS keyframes, zero dependencies, fully customizable speed, direction, rotation and repeat count. Perfect for hero banners, announcements and ticker effects.
Last updated on
"use client";
import { InfiniteRibbon } from "@/components/ui/infinite-ribbon";
export function InfiniteRibbonDemo() {
return (
<div className="relative w-full overflow-hidden rounded-md border bg-background">
<InfiniteRibbon className="bg-yellow-400 text-black dark:bg-yellow-500">
✦ JolyUI · Infinite Ribbon · Build beautiful interfaces ✦
</InfiniteRibbon>
<div className="h-32" />
</div>
);
}Installation
CLI
npx shadcn@latest add "https://jolyui.dev/r/infinite-ribbon"Manual
Copy and paste the following code into your project component/ui/infinite-ribbon.tsx
import type * as React from "react";
import { cn } from "@/lib/utils";
export interface InfiniteRibbonProps {
repeat?: number;
duration?: number;
reverse?: boolean;
rotation?: number;
children: React.ReactNode;
className?: string;
}
const ribbonAnimationStyles = `
@keyframes iconiq-infinite-ribbon {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
@keyframes iconiq-infinite-ribbon-reverse {
from {
transform: translateX(-50%);
}
to {
transform: translateX(0);
}
}
@media (prefers-reduced-motion: reduce) {
.iconiq-infinite-ribbon-track {
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
}
}
`;
export function InfiniteRibbon({
repeat = 5,
duration = 10,
reverse = false,
rotation = 0,
children,
className,
}: InfiniteRibbonProps) {
const repeatCount = Math.max(1, Math.floor(repeat));
const animationName = reverse
? "iconiq-infinite-ribbon-reverse"
: "iconiq-infinite-ribbon";
return (
<div
className={cn(
"w-full max-w-full overflow-hidden bg-foreground py-1 text-background text-lg",
className
)}
style={{ transform: `rotate(${rotation}deg)` }}
>
<span className="sr-only">{children}</span>
<div
aria-hidden="true"
className="iconiq-infinite-ribbon-track flex w-max whitespace-nowrap"
style={
{
"--ribbon-duration": `${Math.max(0.1, duration)}s`,
animation: `${animationName} var(--ribbon-duration) linear infinite`,
} as React.CSSProperties
}
>
{Array.from({ length: repeatCount * 2 }, (_, index) => (
<span className="mr-8 inline-block select-none" key={index}>
{children}
</span>
))}
</div>
<style>{ribbonAnimationStyles}</style>
</div>
);
}Usage
import { InfiniteRibbon } from "@/components/ui/infinite-ribbon";
<InfiniteRibbon repeat={5} duration={12}>
✦ JolyUI · Infinite Ribbon · Build beautiful interfaces ✦
</InfiniteRibbon>Examples
Variants
"use client";
import { InfiniteRibbon } from "@/components/ui/infinite-ribbon";
export function InfiniteRibbonCustomDemo() {
return (
<div className="flex w-full max-w-3xl flex-col items-stretch gap-6">
<div className="space-y-2">
<p className="font-medium text-sm">Slow forward ribbon</p>
<div className="relative overflow-hidden rounded-md border bg-background">
<InfiniteRibbon repeat={4} duration={20}>
🚀 Launch faster · Ship more · Sleep better 🚀
</InfiniteRibbon>
</div>
</div>
<div className="space-y-2">
<p className="font-medium text-sm">Fast reverse ribbon</p>
<div className="relative overflow-hidden rounded-md border bg-background">
<InfiniteRibbon
repeat={6}
duration={6}
reverse
className="bg-pink-500 text-white dark:bg-pink-600"
>
⚡ Fast · Furious · Animated ⚡ Fast · Furious · Animated ⚡
</InfiniteRibbon>
</div>
</div>
<div className="space-y-2">
<p className="font-medium text-sm">Tilted marquee</p>
<div className="relative h-20 overflow-hidden rounded-md border bg-muted/30">
<div className="absolute inset-0 flex items-center">
<InfiniteRibbon
repeat={3}
duration={12}
rotation={-3}
className="w-full bg-emerald-400 text-black dark:bg-emerald-500"
>
🎉 JolyUI · Hand-crafted components for React 🎉
</InfiniteRibbon>
</div>
</div>
</div>
</div>
);
}Overlapping ribbons
"use client";
import { InfiniteRibbon } from "@/components/ui/infinite-ribbon";
export function InfiniteRibbonPreview() {
return (
<div className="relative flex h-72 w-full items-center justify-center overflow-hidden rounded-md border bg-background">
<InfiniteRibbon
className="absolute bg-foreground text-background"
duration={42}
rotation={5}
>
Craft crisp dashboards, lively landing pages, and polished product
flows with components that feel ready from the first click.
</InfiniteRibbon>
<InfiniteRibbon
className="absolute bg-foreground text-background"
duration={42}
reverse
rotation={-5}
>
Craft crisp dashboards, lively landing pages, and polished product
flows with components that feel ready from the first click.
</InfiniteRibbon>
</div>
);
}
export InfiniteRibbonPreview;API Reference
InfiniteRibbon Props
Prop
Type
Notes
- Pure CSS keyframe animation — no
motionor other runtime libraries required - Track is internally duplicated (
repeat * 2) for seamless looping reverseflips the scroll direction without changing the visual orderrotationlets you tilt the ribbon for diagonal marquee effectsprefers-reduced-motionis honored — animation duration collapses to 1ms- Children are wrapped in a
sr-onlyaccessible label so the ribbon stays screen-reader friendly - Great for hero announcements, news tickers, promotional banners and CTA strips
How is this guide?
Highlight Text
Animated text highlighter for React. SVG underlines, boxes, circles, and marker effects. Draw attention to key words with hand-drawn style animations.
Number Counter
Animated number counter for React. Rolling digits, circular progress, and stats counters. Perfect for dashboards, pricing pages, and landing pages.