codefast/ui

Command Palette

Search for a command to run...

Source
Display

Spinner

Indeterminate loading indicator. Wrap children — shown when loading is false.

Examples

Badge

Add a spinner to a badge to indicate a loading state. Place the <Spinner /> before the label with data-icon='inline-start' for a start position, or after the label with data-icon='inline-end' for an end position.

SyncingUpdatingProcessing
21 lines
import { Badge } from "@codefast/ui/badge";
import { Spinner } from "@codefast/ui/spinner";

export function SpinnerBadge() {
  return (
    <div className="flex items-center gap-4 [--radius:1.2rem]">
      <Badge>
        <Spinner data-icon="inline-start" />
        Syncing
      </Badge>
      <Badge variant="secondary">
        <Spinner data-icon="inline-start" />
        Updating
      </Badge>
      <Badge variant="outline">
        <Spinner data-icon="inline-start" />
        Processing
      </Badge>
    </div>
  );
}

Button

Add a spinner to a button to indicate a loading state. Place the <Spinner /> before the label with data-icon='inline-start' for a start position, or after the label with data-icon='inline-end' for an end position.

21 lines
import { Button } from "@codefast/ui/button";
import { Spinner } from "@codefast/ui/spinner";

export function SpinnerButton() {
  return (
    <div className="flex flex-col items-center gap-4">
      <Button disabled size="sm">
        <Spinner data-icon="inline-start" />
        Loading...
      </Button>
      <Button variant="outline" disabled size="sm">
        <Spinner data-icon="inline-start" />
        Please wait
      </Button>
      <Button variant="secondary" disabled size="sm">
        <Spinner data-icon="inline-start" />
        Processing
      </Button>
    </div>
  );
}

Customization

Replace the default spinner icon with any other icon.

14 lines
import { cn } from "@codefast/ui/lib/utils";
import { LoaderIcon } from "lucide-react";

function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
  return <LoaderIcon role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />;
}

export function SpinnerCustom() {
  return (
    <div className="flex items-center gap-4">
      <Spinner />
    </div>
  );
}

Empty

An indicator that can be used to show a loading state.

Processing your request

Please wait while we process your request. Do not refresh the page.

22 lines
import { Button } from "@codefast/ui/button";
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@codefast/ui/empty";
import { Spinner } from "@codefast/ui/spinner";

export function SpinnerEmpty() {
  return (
    <Empty className="w-full">
      <EmptyHeader>
        <EmptyMedia variant="icon">
          <Spinner />
        </EmptyMedia>
        <EmptyTitle>Processing your request</EmptyTitle>
        <EmptyDescription>Please wait while we process your request. Do not refresh the page.</EmptyDescription>
      </EmptyHeader>
      <EmptyContent>
        <Button variant="outline" size="sm">
          Cancel
        </Button>
      </EmptyContent>
    </Empty>
  );
}

Input Group

An indicator that can be used to show a loading state.

Validating...
32 lines
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
  InputGroupTextarea,
} from "@codefast/ui/input-group";
import { Spinner } from "@codefast/ui/spinner";
import { ArrowUpIcon } from "lucide-react";

export function SpinnerInputGroup() {
  return (
    <div className="flex w-full max-w-md flex-col gap-4">
      <InputGroup>
        <InputGroupInput placeholder="Send a message..." disabled />
        <InputGroupAddon align="inline-end">
          <Spinner />
        </InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupTextarea placeholder="Send a message..." disabled />
        <InputGroupAddon align="block-end">
          <Spinner /> Validating...
          <InputGroupButton className="ms-auto" variant="default">
            <ArrowUpIcon />
            <span className="sr-only">Send</span>
          </InputGroupButton>
        </InputGroupAddon>
      </InputGroup>
    </div>
  );
}

RTL

Right-to-left layout support for languages such as Arabic and Hebrew.

Translations are AI-generated for demonstration and may be imperfect.

جاري معالجة الدفع...
١٠٠.٠٠ دولار
49 lines
import { Item, ItemContent, ItemMedia, ItemTitle } from "@codefast/ui/item";
import { Spinner } from "@codefast/ui/spinner";

import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";

const translations: Translations = {
  en: {
    dir: "ltr",
    values: {
      title: "Processing payment...",
      amount: "$100.00",
    },
  },
  ar: {
    dir: "rtl",
    values: {
      title: "جاري معالجة الدفع...",
      amount: "١٠٠.٠٠ دولار",
    },
  },
  he: {
    dir: "rtl",
    values: {
      title: "מעבד תשלום...",
      amount: "$100.00",
    },
  },
};

export function SpinnerRtl() {
  const { dir, t } = useTranslation(translations, "ar");

  return (
    <div className="flex w-full max-w-xs flex-col gap-4 [--radius:1rem]" dir={dir}>
      <Item variant="muted" dir={dir}>
        <ItemMedia>
          <Spinner />
        </ItemMedia>
        <ItemContent>
          <ItemTitle className="line-clamp-1">{t.title}</ItemTitle>
        </ItemContent>
        <ItemContent className="flex-none justify-end">
          <span className="text-sm tabular-nums">{t.amount}</span>
        </ItemContent>
      </Item>
    </div>
  );
}

Size

Use the size-* utility class to change the size of the spinner.

12 lines
import { Spinner } from "@codefast/ui/spinner";

export function SpinnerSize() {
  return (
    <div className="flex items-center gap-6">
      <Spinner className="size-3" />
      <Spinner className="size-4" />
      <Spinner className="size-6" />
      <Spinner className="size-8" />
    </div>
  );
}

Usage

The minimal import and composition — see Examples below for styled, real-world variants.

5 lines
import { Spinner } from "@codefast/ui/spinner";

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

Anatomy

How the parts nest — every slot the component exposes, in composition order.

Spinner

Features

  • With children and loading=true, it hides them invisibly (keeping their layout size) and overlays the spinner on top — a VisuallyHidden copy keeps the label announced to screen readers.
  • loading={false} renders the children directly in place of the spinner, so a single component can toggle between the two states.
  • Respects prefers-reduced-motion — the animation pauses automatically.

API reference

Props for each part of the component. All native element props are also forwarded.

Spinner

An indeterminate loading indicator.

loadingboolean

When false, renders children instead of the spinner.

Defaulttrue

childrenReactNode

Used as a visually-hidden label while spinning.

classNamestring

Set the size (e.g. size-5) and colour.

Accessibility

Built to be keyboard-navigable and screen-reader friendly out of the box.

  • Give the spinner a label via children so its purpose is announced.
  • Prefer a Spinner over a fake progress bar when you don’t know the duration.
  • Respect reduced motion — the animation is paused under prefers-reduced-motion.

Guidelines

Conventions that keep usage consistent across an app.

Do

  • Use for short, indeterminate waits.
  • Pair with disabled controls while an action is in flight.

Don’t

  • Don’t use a spinner for long, measurable tasks — use Progress.
  • Don’t show a bare spinner with no context about what’s loading.

Explore further

Ready to integrate?

Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.