GitHub

Ring

A circular indeterminate loader for inline actions, page transitions, and general pending states.

import { Ring } from "@/components/loading-ui/ring";

export function RingDemo() {

Installation

pnpm dlx shadcn add @loading-ui/ring
components/ring.tsx
import { cn } from "@/lib/utils";

function Ring({ className, ...props }: React.ComponentProps<"svg">) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className={cn("animate-spin", className)}
      {...props}
    >
      <path
        d="M21 12.0004C20.9999 13.901 20.3981 15.7528 19.2809 17.2904C18.1637 18.8279 16.5885 19.9723 14.7809 20.5596C12.9733 21.1469 11.0262 21.1468 9.21864 20.5594C7.41109 19.9721 5.83588 18.8276 4.71876 17.29C3.60165 15.7523 2.99999 13.9005 3 11.9999C3.00001 10.0993 3.60171 8.24755 4.71884 6.70994C5.83598 5.17233 7.4112 4.02785 9.21877 3.44052C11.0263 2.85319 12.9734 2.85316 14.781 3.44044"
        stroke="currentColor"
        strokeWidth="2"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export { Ring };

Usage

import { Ring } from "@/components/ring";
<Ring className="size-4" />

Why use it

Use Ring when you want a loader that reads clearly at a glance and fits naturally into buttons, cards, dialogs, and route-level pending UI. It works well when the user only needs confirmation that work is in progress, not a precise percentage.

Examples

Inline action

Use Ring next to a label when an action is pending.

import { Ring } from "@/components/ring";
 
export function SaveButton({ isSaving }: { isSaving: boolean }) {
  return (
    <button
      type="button"
      disabled={isSaving}
      className="inline-flex items-center gap-2 rounded-md border px-3 py-2"
    >
      {isSaving ? <Ring className="size-4" /> : null}
      <span>{isSaving ? "Saving..." : "Save changes"}</span>
    </button>
  );
}

Centered fallback

Use it as a simple fallback for route or section loading.

import { Ring } from "@/components/ring";
 
export function LoadingSection() {
  return (
    <div className="flex items-center justify-center py-12">
      <Ring className="size-6 text-muted-foreground" />
    </div>
  );
}

Size and color

Ring inherits currentColor, so it responds well to utility classes.

<Ring className="size-4 text-muted-foreground" />
<Ring className="size-6 text-foreground" />
<Ring className="size-8 text-primary" />

Accessibility

On its own, Ring is purely visual. When the state is not already obvious from nearby text, pair it with accessible copy such as Saving..., Loading results..., or screen-reader-only status text.

Customization

The component is just an animated SVG path, so customization is straightforward:

  • change the stroke width for a heavier or lighter feel
  • slow down or replace animate-spin
  • wrap it in a status component with labels or motion preferences
  • Spokes for a lighter, more segmented spinner