Aura Design System

Marquee

A looping row of items that pauses for reduced motion.

Preview

Loading...
import { Marquee } from "@/components/ui/Marquee";

export function MarqueeDemo() {
  return (
  <Marquee>
    <span className="rounded-sm bg-accent-3 px-1 py-0.5">Color</span>
    <span className="rounded-sm bg-gray-3 px-1 py-0.5">Type</span>
    <span className="rounded-sm bg-accent-3 px-1 py-0.5">Space</span>
    <span className="rounded-sm bg-gray-3 px-1 py-0.5">Motion</span>
  </Marquee>
)
}

Installation

Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces

pnpm dlx shadcn@latest add @aura/marquee

Manual

Copy and paste the class names utility into your utils/class-names.ts file.

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Copy and paste the Marquee component into your components/ui/Marquee.tsx file.

"use client";

import * as React from "react";
import { cn } from "@/utils/class-names";

function Marquee({
  className,
  children,
  label = "Marquee",
}: {
  className?: string;
  children: React.ReactNode;
  label?: string;
}) {
  const reduced = useReduced();
  return (
    <div
      data-slot="marquee"
      aria-label={label}
      className={cn("overflow-hidden", className)}
    >
      <div data-animate={reduced ? undefined : "marquee"} className="flex w-max gap-2">
        <div className="flex gap-2">{children}</div>
        <div className="flex gap-2" aria-hidden>
          {children}
        </div>
      </div>
      <style>{`@keyframes aura-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } } [data-slot="marquee"] [data-animate="marquee"] { animation: aura-marquee 12s linear infinite; } @media (prefers-reduced-motion: reduce) { [data-slot="marquee"] [data-animate="marquee"] { animation: none; } }`}</style>
    </div>
  );
}

function useReduced() {
  const [reduced, setReduced] = React.useState(false);
  React.useEffect(() => {
    const query = window.matchMedia("(prefers-reduced-motion: reduce)");
    const update = () => setReduced(query.matches);
    update();
    query.addEventListener("change", update);
    return () => query.removeEventListener("change", update);
  }, []);
  return reduced;
}

export { Marquee };