shadimage
Beste UI Components
componentbadge10source: ok · hit

Rotating Text Seal

A rotating circular text badge with a center icon for editorial seals and open for work stamps.

npx shadcn add "https://ui.beste.co/component/r/badge10.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
clsxtailwind-mergelucide-react

Files1 · source cached 10/9/2026, 4:13:06 PM

registry-components/badge10/badge10.tsx
import { Asterisk, type LucideIcon } from "lucide-react";
import { useId } from "react";
import { cn } from "@/lib/utils";

type Tone = "foreground" | "muted" | "primary";

interface Badge10Props {
  /** Circular text. End with a separator (e.g. "Open for work • ") so the loop reads cleanly */
  text: string;
  /** Diameter in pixels */
  size?: number;
  /** Seconds per full rotation */
  duration?: number;
  /** Center icon (defaults to Asterisk) */
  icon?: LucideIcon;
  /** Text and icon color */
  tone?: Tone;
  /** Additional classes merged onto the root */
  className?: string;
}

const toneStyles: Record<Tone, string> = {
  foreground: "text-foreground",
  muted: "text-muted-foreground",
  primary: "text-primary",
};

export const badge10Demo: Badge10Props = {
  text: "Open for work • Available now • ",
  size: 120,
};

/**
 * An editorial rotating seal: text runs around a circle with an
 * icon at the center. Rotation pauses entirely under prefers-reduced-motion.
 */
export function Badge10({
  text,
  size = 112,
  duration = 14,
  icon: Icon = Asterisk,
  tone = "foreground",
  className,
}: Badge10Props) {
  const pathId = useId();

  return (
    <span
      className={cn(
        "relative inline-flex items-center justify-center",
        toneStyles[tone],
        className
      )}
      style={{ width: size, height: size }}
    >
      <svg
        viewBox="0 0 100 100"
        className="size-full motion-safe:animate-[spin_14s_linear_infinite]"
        style={{ animationDuration: `${duration}s` }}
        aria-hidden="true"
      >
        <defs>
          <path
            id={pathId}
            d="M 50,50 m -37,0 a 37,37 0 1,1 74,0 a 37,37 0 1,1 -74,0"
            fill="none"
          />
        </defs>
        <text
          fill="currentColor"
          fontSize="11.5"
          // Spread to the full circumference so the ring closes whatever the text length
          textLength={232}
          lengthAdjust="spacing"
          className="font-medium"
        >
          <textPath href={`#${pathId}`}>{text}</textPath>
        </text>
      </svg>
      <Icon className="absolute size-5" aria-hidden="true" />
      <span className="sr-only">{text}</span>
    </span>
  );
}

More from Beste UI Components

Browse all components in this library →