Qure UI
Components

Spinner

An indeterminate wait, for when there is no progress to report.

Loading
import { Spinner } from '@/registry/qure/ui/spinner'export default function SpinnerDemo() {  return <Spinner />}

Installation

npx shadcn@latest add https://qure-ui.qure.ai/r/spinner.json

Usage

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

Reach for Progress whenever you know how far along you are. A spinner says only "something is happening"; a bar that fills says how much longer, and a reader waiting on a study upload wants the second one.

Sizes

Five, matching the icon steps used elsewhere — 12, 16, 20, 24 and 32 pixels.

Loading
import { Spinner } from '@/registry/qure/ui/spinner'export default function SpinnerSizes() {  return (    <div className="flex items-center gap-6">      <Spinner size="xs" label={null} />      <Spinner size="sm" label={null} />      <Spinner size="md" label={null} />      <Spinner size="lg" label={null} />      <Spinner size="xl" label="Loading" />    </div>  )}
SizeUse
xsinside a field's trailing slot, or beside a line of small text
sminside a button, or against body text
mdthe default; a panel or a card
lgan empty region waiting to be filled
xla whole page or a modal body

Colour

The spinner draws in currentColor, so it takes the colour of whatever contains it and never needs to be told which surface it landed on.

Signing
import { Spinner } from '@/registry/qure/ui/spinner'export default function SpinnerColour() {  return (    <div className="flex items-center gap-6">      <Spinner label={null} />      <Spinner className="text-primary" label={null} />      <Spinner className="text-muted-foreground" label={null} />      <div className="bg-primary text-primary-foreground flex items-center gap-2 rounded-md px-3 py-2">        {/* No colour set: it takes the foreground of the surface it landed on. */}        <Spinner size="sm" label={null} />        <span className="text-label-base">Signing</span>      </div>    </div>  )}

In a button

import { Button } from '@/registry/qure/ui/button'import { Spinner } from '@/registry/qure/ui/spinner'export default function SpinnerButton() {  return (    <div className="flex flex-wrap items-center gap-3">      {/* `label={null}`: the button's own text already says what is happening,          and `disabled` is what tells assistive tech it cannot be pressed. */}      <Button disabled>        <Spinner size="sm" label={null} />        Signing report      </Button>      <Button variant="secondary" disabled>        <Spinner size="sm" label={null} />        Uploading      </Button>      <Button variant="tertiary" size="icon-md" disabled aria-label="Refreshing">        <Spinner size="sm" label={null} />      </Button>    </div>  )}

Keep the label. A button whose text is replaced by a spinner loses the only description of what you are waiting for, and it changes width as it does so.

disabled is what stops the second press and what tells assistive technology the control is unavailable — the spinner is the visible half of that, not the mechanism.

Announcing the wait

By default the spinner carries role="status" and a visually hidden "Loading", so a screen reader says something when it appears. Pass your own label to be specific:

<Spinner label="Loading worklist" />

Pass label={null} when there is already text beside it saying the same thing. Two announcements of one wait is worse than one.

Loading priors…
Loading worklist
import { Spinner } from '@/registry/qure/ui/spinner'export default function SpinnerInline() {  return (    <div className="flex w-72 flex-col gap-4">      {/* Beside its own explanation. The spinner is decorative here — the text          is the announcement, so the spinner must not repeat it. */}      <div className="text-muted-foreground flex items-center gap-2">        <Spinner size="sm" label={null} />        <span className="text-body-small">Loading priors…</span>      </div>      {/* Filling a region that has nothing else in it yet. Here the spinner is          the only thing that can carry the message, so it keeps its label. */}      <div className="flex h-28 items-center justify-center rounded-lg border">        <Spinner size="lg" label="Loading worklist" />      </div>    </div>  )}

A spinner that never resolves is worse than no spinner. If a request can fail, render Empty or an Alert on failure — leaving the spinner running reads as a hung page and the reader has no way to tell the difference.

Reduced motion

Under prefers-reduced-motion the spinner slows from 900ms to 2.4s per turn rather than stopping. A frozen spinner looks like a crash, which is the opposite of what it is for.

API Reference

Prop

Type

On this page