Skip to content

Spinner

An animated loading indicator for pending submits, fetches, and inline busy states — a drop-in icon for any neobrutalist control.

import {
  Item,
  ItemContent,

The spinner is a single animated icon that signals an in-flight operation. It wraps the LoaderIcon from lucide-react with animate-spin and ships role="status" plus an aria-label out of the box — no primitive library involved, so both backends share the same implementation, and it inherits currentColor to match whatever neobrutalist control it sits in.

Reach for it when:

  • Pending submits — swap it into a button while the mutation runs; the label stays put.
  • Inline fetch states — badges, input groups, and table cells waiting on data.
  • Section-level loads — pair it with Empty while a panel streams in.

Installation

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/spinner.json
npx shadcn@latest add https://neobrutalism.com/r/base/spinner.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/spinner.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/spinner.json

Copy and paste the following code into your project.

components/ui/spinner.tsx
import { Loader2Icon } from "lucide-react"

import { cn } from "@/lib/utils"

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

export { Spinner }

Update the import paths to match your project setup.

Usage

import { Spinner } from "@/components/ui/spinner"
<Spinner />

Customization

You can replace the default spinner icon with any other icon by editing the Spinner component.

import { LoaderIcon } from "lucide-react"

import { cn } from "@/lib/utils"
components/ui/spinner.tsx
import { LoaderIcon } from "lucide-react"
 
import { cn } from "@/lib/utils"
 
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
  return (
    <LoaderIcon
      role="status"
      aria-label="Loading"
      className={cn("size-4 animate-spin", className)}
      {...props}
    />
  )
}
 
export { Spinner }

Examples

Size

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

import { Spinner } from "@/components/ui/spinner"

export function SpinnerSize() {

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.

import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"

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.

import { Badge } from "@/components/ui/badge"
import { Spinner } from "@/components/ui/spinner"

Input Group

import { ArrowUpIcon } from "lucide-react"

import {

Empty

import { Button } from "@/components/ui/button"
import {
  Empty,

RTL

To enable RTL support in Neobrutalism.com, see the RTL configuration guide.

"use client"

import * as React from "react"

Accessibility

The spinner renders role="status" with aria-label="Loading", so screen readers announce the loading state without extra wiring. When nearby text already describes the state ("Saving…"), pass aria-hidden="true" to the spinner instead — one announcement is enough. For a whole region that swaps into a loading state, set aria-busy="true" on the container.