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 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 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