sync

Spinner

Compact loading indicator for small areas.

Spinner Examples

Copy-ready examples with live previews.

Basic

Default spinner with no props.

Live preview

Default spinner

Basic

import Spinner from "@/_components/spinner/Spinner";

export default function Example() {
  return <Spinner />;
}

Sizes

Choose the size that best matches your layout density.

Live preview

small
medium
large

Sizes

import Spinner from "@/packages/ui/src/_components/spinner/Spinner";

export default function Example() {
  return (
    <div style={{ display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap" }}>
      <Spinner size="small" />
      <Spinner size="medium" />
      <Spinner size="large" />
    </div>
  );
}

Variants

Semantic colors. The mapping is CSS-driven via `variantClasses`.

Live preview

primary
secondary
success
warning
danger
info

Variants

import Spinner from "@/packages/ui/src/_components/spinner/Spinner";

export default function Example() {
  return (
    <div style={{ display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap" }}>
      <Spinner variant="primary" />
      <Spinner variant="secondary" />
      <Spinner variant="success" />
      <Spinner variant="warning" />
      <Spinner variant="danger" />
      <Spinner variant="info" />
    </div>
  );
}

Animation speed

Tune the perceived responsiveness of the loading state.

Live preview

slow
medium
fast
ultra

Animation speed

import Spinner from "@/packages/ui/src/_components/spinner/Spinner";

export default function Example() {
  return (
    <div style={{ display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap" }}>
      <Spinner animationSpeed="slow" />
      <Spinner animationSpeed="medium" />
      <Spinner animationSpeed="fast" />
      <Spinner animationSpeed="ultra" />
    </div>
  );
}

Critical combinations

A few combinations worth validating visually.

Live preview

large + primary + ultra
medium + success + fast
small + danger + medium
large + warning + ultra (stress)
small + info + fast (stress)
medium + secondary

Critical combos

import Spinner from "@/packages/ui/src/_components/spinner/Spinner";

export default function Example() {
  return (
    <div style={{ display: "grid", gap: 12 }}>
      <Spinner size="large" variant="primary" animationSpeed="ultra" />
      <Spinner size="medium" variant="success" animationSpeed="fast" />
      <Spinner size="small" variant="danger" animationSpeed="medium" />

      <Spinner size="large" variant="warning" animationSpeed="ultra" />
      <Spinner size="small" variant="info" animationSpeed="fast" />
      <Spinner size="medium" variant="secondary" />
    </div>
  );
}

Inline usage

Because Spinner renders a `<span>`, it can be used inline.

Live preview

Loading your profile
Fetching latest data…

Inline patterns

import Spinner from "@/packages/ui/src/_components/spinner/Spinner";

export default function Example() {
  return (
    <div style={{ display: "grid", gap: 10 }}>
      <div style={{ opacity: 0.85 }}>
        Loading your profile{" "}
        <span style={{ marginInlineStart: 8 }}>
          <Spinner size="small" />
        </span>
      </div>

      <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
        <Spinner size="small" variant="info" />
        <div style={{ opacity: 0.8 }}>Fetching latest data…</div>
      </div>
    </div>
  );
}

Button loading (pattern)

Common UI pattern: show a spinner while an action is pending.

Keep widths stable so the layout does not jump when toggling the loading UI.

Live preview

Pattern: keep the button width stable and swap the content.

Button loading

import { useState } from "react";
import Spinner from "@/packages/ui/src/_components/spinner/Spinner";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";

export default function Example() {
  const [busy, setBusy] = useState(false);

  return (
    <div style={{ display: "grid", gap: 12 }}>
      <Button variant="primary" onClick={() => setBusy((v) => !v)}>
        Toggle loading
      </Button>

      <div style={{ width: 280, maxWidth: "100%" }}>
        {busy ? (
          <Button variant="primary" disabled>
            <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
              <Spinner size="small" variant="primary" animationSpeed="fast" />
              <span>Processing…</span>
            </div>
          </Button>
        ) : (
          <Button variant="primary">Continue</Button>
        )}
      </div>
    </div>
  );
}

Playground (interactive)

Quick way to validate your CSS classes for size/variant/speed.

Live preview

Result: medium + primary + medium

Playground

import { useState } from "react";
import Spinner from "@/packages/ui/src/_components/spinner/Spinner";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";

type SpinnerSize = "small" | "medium" | "large";
type SpinnerVariant = "primary" | "secondary" | "success" | "warning" | "danger" | "info";
type SpinnerSpeed = "slow" | "medium" | "fast" | "ultra";

export default function Example() {
  const [size, setSize] = useState<SpinnerSize>("medium");
  const [variant, setVariant] = useState<SpinnerVariant>("primary");
  const [speed, setSpeed] = useState<SpinnerSpeed>("medium");

  return (
    <div style={{ display: "grid", gap: 12 }}>
      <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
        <Button variant={size === "small" ? "primary" : "transparent"} onClick={() => setSize("small")}>small</Button>
        <Button variant={size === "medium" ? "primary" : "transparent"} onClick={() => setSize("medium")}>medium</Button>
        <Button variant={size === "large" ? "primary" : "transparent"} onClick={() => setSize("large")}>large</Button>
      </div>

      <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
        <Button variant={variant === "primary" ? "primary" : "transparent"} onClick={() => setVariant("primary")}>primary</Button>
        <Button variant={variant === "secondary" ? "primary" : "transparent"} onClick={() => setVariant("secondary")}>secondary</Button>
        <Button variant={variant === "success" ? "primary" : "transparent"} onClick={() => setVariant("success")}>success</Button>
        <Button variant={variant === "warning" ? "primary" : "transparent"} onClick={() => setVariant("warning")}>warning</Button>
        <Button variant={variant === "danger" ? "primary" : "transparent"} onClick={() => setVariant("danger")}>danger</Button>
        <Button variant={variant === "info" ? "primary" : "transparent"} onClick={() => setVariant("info")}>info</Button>
      </div>

      <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
        <Button variant={speed === "slow" ? "primary" : "transparent"} onClick={() => setSpeed("slow")}>slow</Button>
        <Button variant={speed === "medium" ? "primary" : "transparent"} onClick={() => setSpeed("medium")}>medium</Button>
        <Button variant={speed === "fast" ? "primary" : "transparent"} onClick={() => setSpeed("fast")}>fast</Button>
        <Button variant={speed === "ultra" ? "primary" : "transparent"} onClick={() => setSpeed("ultra")}>ultra</Button>
      </div>

      <div style={{ padding: 12, border: "1px solid rgba(0,0,0,0.12)", borderRadius: 12 }}>
        <Spinner size={size} variant={variant} animationSpeed={speed} />
      </div>
    </div>
  );
}

On this page

Spinner Props

Spinner — Props
PropTypeRequiredDefaultDescription
variant"primary" | "secondary" | "success" | "warning" | "danger" | "info"NoundefinedAdds a semantic color class (from `variantClasses`).
size"small" | "medium" | "large"NoundefinedAdds a sizing class (from `sizeClasses`).
animationSpeed"slow" | "medium" | "fast" | "ultra"NoundefinedAdds an animation speed class (from `animationClasses`).