Build websites 10x faster with HextaUI Blocks — Learn more
UI/UI

Loader

A simple and elegant loading spinner component with multiple sizes and color variants.

<div className="flex items-center gap-6 flex-wrap">
  <Loader size="xs" />
  <Loader size="sm" />
  <Loader size="md" />
  <Loader size="lg" />
  <Loader size="xl" />
</div>

Installation

Install following dependencies:

npm install class-variance-authority
pnpm add class-variance-authority
yarn add class-variance-authority
bun add class-variance-authority

Copy and paste the following code into your project.

components/ui/loader.tsx
"use client";

import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const loaderVariants = cva("inline-block", {
  variants: {
    size: {
      xs: "h-3 w-3",
      sm: "h-4 w-4",
      md: "h-5 w-5",
      lg: "h-6 w-6",
      xl: "h-8 w-8",
    },
    variant: {
      default: "text-[hsl(var(--hu-foreground))]",
      primary: "text-[hsl(var(--hu-primary))]",
      secondary: "text-[hsl(var(--hu-secondary-foreground))]",
      muted: "text-[hsl(var(--hu-muted-foreground))]",
    },
  },
  defaultVariants: {
    size: "md",
    variant: "default",
  },
});

export interface LoaderProps
  extends React.SVGAttributes<SVGSVGElement>,
    VariantProps<typeof loaderVariants> {}

const Loader = React.forwardRef<SVGSVGElement, LoaderProps>(
  ({ className, size, variant, ...props }, ref) => {
    const [isMounted, setIsMounted] = React.useState(false);

    React.useEffect(() => {
      setIsMounted(true);
    }, []);

    return (
      <svg
        ref={ref}
        className={cn(
          loaderVariants({ size, variant }),
          isMounted && "animate-spin",
          className,
        )}
        xmlns="http://www.w3.org/2000/svg"
        fill="none"
        viewBox="0 0 24 24"
        role="status"
        aria-label="Loading"
        suppressHydrationWarning
        {...props}
      >
        <circle
          className="opacity-25"
          cx="12"
          cy="12"
          r="10"
          stroke="currentColor"
          strokeWidth="4"
        />{" "}
        <path
          className="opacity-75"
          fill="currentColor"
          d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
        />
      </svg>
    );
  },
);

Loader.displayName = "Loader";

export { Loader, loaderVariants };
npx hextaui@latest add loader
pnpm dlx hextaui@latest add loader
yarn dlx hextaui@latest add loader
bun x hextaui@latest add loader

Usage

import { Loader } from "@/components/ui/Loader";
<Loader />
<Loader size="lg" />
<Loader variant="primary" />
<Loader size="sm" variant="muted" />

Examples

Sizes

Extra Small

Small

Medium

Large

Extra Large

<div className="flex items-center gap-6 flex-wrap">
  <Loader size="xs" />
  <Loader size="sm" />
  <Loader size="md" />
  <Loader size="lg" />
  <Loader size="xl" />
</div>

Variants

Default

Primary

Secondary

Muted

<div className="flex items-center gap-6 flex-wrap">
  <Loader variant="default" />
  <Loader variant="primary" />
  <Loader variant="secondary" />
  <Loader variant="muted" />
</div>

With Loading Text

Loading...

Processing your request

Uploading files

Saving changes
<div className="space-y-4">
  <div className="flex items-center gap-3">
    <Loader size="sm" />
    <span className="text-sm text-muted-foreground">Loading...</span>
  </div>
  <div className="flex items-center gap-3">
    <Loader variant="primary" />
    <span className="text-sm">Processing your request</span>
  </div>
  <div className="flex items-center gap-3">
    <Loader size="sm" variant="muted" />
    <span className="text-sm text-muted-foreground">Uploading files</span>
  </div>
  <div className="flex items-center gap-3">
    <Loader variant="secondary" />
    <span className="text-sm">Saving changes</span>
  </div>
</div>

Button Integration

<div className="space-y-4">
  <button
    className="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-[var(--radius)] hover:bg-primary/90 transition-colors disabled:opacity-50"
    disabled
  >
    <Loader size="sm" className="text-current" />
    <span>Loading...</span>
  </button>
  <button
    className="flex items-center gap-2 px-4 py-2 bg-secondary text-secondary-foreground rounded-[var(--radius)] hover:bg-secondary/90 transition-colors disabled:opacity-50"
    disabled
  >
    <Loader size="sm" className="text-current" />
    <span>Processing</span>
  </button>
  <button
    className="flex items-center gap-2 px-4 py-2 border border-border rounded-[var(--radius)] hover:bg-accent transition-colors disabled:opacity-50"
    disabled
  >
    <Loader size="sm" />
    <span>Uploading</span>
  </button>
</div>

Content Loading States

Loading content...

Fetching data...

<div className="space-y-6">
  <div className="border border-border rounded-2xl p-8">
    <div className="flex items-center justify-center h-32">
      <div className="text-center space-y-4">
        <Loader size="xl" variant="primary" />
        <p className="text-sm text-muted-foreground">Loading content...</p>
      </div>
    </div>
  </div>
  <div className="border border-border rounded-2xl p-6">
    <div className="flex items-center justify-center h-24">
      <div className="text-center space-y-3">
        <Loader size="lg" variant="muted" />
        <p className="text-xs text-muted-foreground">Fetching data...</p>
      </div>
    </div>
  </div>
</div>

Inline Loading

Please wait while we process your request

Status:

Connecting...

Upload progress:

47% complete

<div className="space-y-4">
  <div className="text-sm">
    Please wait while we process your request{" "}
    <Loader size="xs" className="inline-block align-text-bottom mx-1" />
  </div>
  <div className="flex items-center gap-2 text-sm">
    <span>Status:</span>
    <Loader size="xs" variant="primary" />
    <span className="text-muted-foreground">Connecting...</span>
  </div>
  <div className="flex items-center gap-2 text-sm">
    <span>Upload progress:</span>
    <Loader size="xs" variant="secondary" />
    <span className="text-muted-foreground">47% complete</span>
  </div>
</div>

Props

PropTypeDefault
className?
string
undefined
variant?
"default" | "primary" | "secondary" | "muted"
"default"
size?
"xs" | "sm" | "md" | "lg" | "xl"
"md"
Edit on GitHub

Last updated on