Skip to content
Docs
Announcement Ribbon

Announcement Ribbon

A sticky scrolling banner for top-of-page announcements.

Loading...

Installation

CLI

pnpm dlx shadcn@latest add https://animata.design/r/container/announcement-ribbon.json

Manual

Install dependencies

This component uses the Marquee component for the scrolling track. Make sure it's in your project first.

Run the following command

mkdir -p components/animata/container && touch components/animata/container/announcement-ribbon.tsx

Paste the code

Open the newly created file and paste the following code:

"use client";
 
import Link from "next/link";
 
import Marquee from "@/animata/container/marquee";
import { cn } from "@/lib/utils";
 
interface AnnouncementRibbonProps extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * Content to scroll in the ribbon. Accepts any React node.
   * Defaults to the May 2026 changelog announcement.
   */
  message?: React.ReactNode;
 
  /**
   * Label shown in the static left badge. Pass `null` to hide it.
   * @default "NEW"
   */
  badge?: string | null;
 
  /**
   * Text for the right-side CTA link. Pass `null` to hide it.
   * @default "Learn more"
   */
  ctaText?: string | null;
 
  /**
   * URL for the CTA link.
   * @default "/docs/changelog/2026-05"
   */
  ctaHref?: string;
 
  /**
   * Number of times the message is repeated to fill the track.
   * @default 5
   */
  repeat?: number;
 
  /**
   * Pause scrolling when the user hovers over the ribbon.
   * @default true
   */
  pauseOnHover?: boolean;
}
 
function DefaultMessage() {
  return (
    <span>
      <span className="whitespace-nowrap px-12 font-(family-name:--font-display) font-light text-neutral-900">
        New components and live demos
      </span>
      <span className="text-neutral-900">&middot;</span>
    </span>
  );
}
 
export default function AnnouncementRibbon({
  message,
  badge = "NEW",
  ctaText = "Learn more",
  ctaHref = "/docs/changelog/2026-05",
  repeat = 5,
  pauseOnHover = true,
  className,
  ...props
}: AnnouncementRibbonProps) {
  const content = message ?? <DefaultMessage />;
 
  return (
    <div
      className={cn(
        "relative flex h-11 w-full items-center overflow-hidden",
        "bg-[#ffcc00]",
        "border-b border-black/8",
        className,
      )}
      {...props}
    >
      {/* Badge */}
      {badge && (
        <div className="relative z-30 flex bg-[#ffcc00] shrink-0 items-center self-stretch border-r border-black/8 px-4">
          <span className="rounded-full bg-black/10 px-2.5 py-px font-mono text-[10px] font-semibold uppercase tracking-widest text-neutral-900">
            {badge}
          </span>
        </div>
      )}
 
      <div className="flex-1 overflow-hidden">
        <Marquee repeat={repeat} pauseOnHover={pauseOnHover} applyMask={false}>
          {content}
        </Marquee>
      </div>
 
      {/* CTA */}
      {ctaText && ctaHref && (
        <Link
          href={ctaHref}
          className="group/cta relative bg-[#ffcc00] z-30 flex shrink-0 items-center gap-1.5 self-stretch border-l border-black/8 px-4 font-mono text-[10px] font-semibold uppercase tracking-widest text-neutral-800/60 transition-colors hover:text-neutral-900"
        >
          {ctaText}
          <svg
            className="h-3 w-3 transition-transform group-hover/cta:translate-x-0.5"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
            strokeWidth={2}
            aria-hidden
            role="presentation"
          >
            <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
          </svg>
        </Link>
      )}
    </div>
  );
}