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

Edit on GitHub
"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>✦ 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-yellow-400 py-1 text-black text-lg dark:bg-yellow-500 dark:text-black",
        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 flex-col items-stretch justify-center gap-8">
      <div className="space-y-2">
        <p className="font-medium text-sm">Slow forward ribbon</p>
        <InfiniteRibbon repeat={4} duration={20}>
          🚀 Launch faster · Ship more · Sleep better 🚀
        </InfiniteRibbon>
      </div>
 
      <div className="space-y-2">
        <p className="font-medium text-sm">Fast reverse ribbon</p>
        <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 className="space-y-2">
        <p className="font-medium text-sm">Tilted marquee</p>
        <div className="rounded-md border bg-muted/30 py-6">
          <InfiniteRibbon repeat={3} duration={12} rotation={-3} className="bg-emerald-400 text-black dark:bg-emerald-500">
            🎉 JolyUI · Hand-crafted components for React 🎉
          </InfiniteRibbon>
        </div>
      </div>
    </div>
  );
}

API Reference

InfiniteRibbon Props

Prop

Type

Notes

  • Pure CSS keyframe animation — no motion or other runtime libraries required
  • Track is internally duplicated (repeat * 2) for seamless looping
  • reverse flips the scroll direction without changing the visual order
  • rotation lets you tilt the ribbon for diagonal marquee effects
  • prefers-reduced-motion is honored — animation duration collapses to 1ms
  • Children are wrapped in a sr-only accessible label so the ribbon stays screen-reader friendly
  • Great for hero announcements, news tickers, promotional banners and CTA strips

How is this guide?

On this page