Skip to content
omaris

Communication

Spinner

A small busy indicator for inline use — at the end of a row, on a chip, beside a line of text, in the corner of a card that is refetching.

import { Spinner } from 'omaris'
Learn

Examples

Basic

A spinner says "working" in the space of an icon. It is a live status, so a screen reader hears its label — "Loading" unless you name the wait.

Loading Loading orders
<script lang="ts">	import { Spinner } from 'omaris';</script>​<Spinner /><Spinner label="Loading orders" size="lg" />

Sizes and tones

Four sizes for four places — a chip, a line of text, a row, a card — and the library's tones. tone="current" borrows the colour of the text around it.

Loading xs
Loading sm
Loading md
Loading lg
Loading, primary Loading, tertiary Loading, success Loading, warning Loading, destructive Loading, current
<script lang="ts">	import { Spinner, Text } from 'omaris';​	const TONES = ['primary', 'tertiary', 'success', 'warning', 'destructive'] as const;</script>​<div class="flex flex-col items-center gap-6">	<div class="flex items-end gap-6">		{#each ['xs', 'sm', 'md', 'lg'] as const as size (size)}			<div class="flex flex-col items-center gap-2">				<Spinner {size} />				<Text variant="label-sm" tone="muted">{size}</Text>			</div>		{/each}	</div>	<div class="flex flex-wrap items-center justify-center gap-5">		{#each TONES as tone (tone)}			<Spinner {tone} label="Loading, {tone}" />		{/each}		<span class="rounded-shape-md bg-inverse-surface p-2 text-inverse-surface-foreground">			<Spinner tone="current" label="Loading, current" />		</span>	</div></div>

In context

Where it belongs: at the end of the row that is syncing, beside the line that is checking, in the corner of a card that is refetching while its old figure stays on screen.

orders-2026-09.csv

Syncing Syncing orders-2026-09.csv
Checking

Checking the name is free…

Revenue this week Refreshing revenue

4,820,000 IQD

<script lang="ts">	import { Card, CardContent, Spinner, Text } from 'omaris';</script>​<div class="flex w-full max-w-sm flex-col gap-4">	<div class="flex items-center gap-3 rounded-shape-md border border-border px-4 py-3">		<Text variant="body-md" class="min-w-0 flex-1" lines={1}>orders-2026-09.csv</Text>		<Text variant="label-sm" tone="muted">Syncing</Text>		<Spinner size="sm" label="Syncing orders-2026-09.csv" />	</div>​	<div class="flex items-center gap-2">		<Spinner size="xs" tone="current" label="Checking" />		<Text variant="body-sm" tone="muted">Checking the name is free…</Text>	</div>​	<Card variant="outlined">		<CardContent class="flex flex-col gap-1">			<div class="flex items-center justify-between gap-3">				<Text variant="label-md" tone="muted">Revenue this week</Text>				<Spinner size="sm" label="Refreshing revenue" />			</div>			<Text variant="headline-md" tabular>4,820,000 IQD</Text>		</CardContent>	</Card></div>

Overridden

Every part is reachable: class sizes the root past lg, classes thickens the arc or drops the track, and an aria-label the caller sets is kept.

Loading a big thing Loading Refreshing
<script lang="ts">	import { Spinner } from 'omaris';</script>​<Spinner class="size-12" label="Loading a big thing" /><Spinner	size="lg"	tone="tertiary"	classes={{ track: 'hidden', indicator: 'stroke-[3.5]' }}	label="Loading"/><Spinner size="sm" aria-label="Refreshing prices" label="Refreshing" />

When to use it

Use it for

  • A small wait inside something that is already on screen — the end of a row that is syncing, a chip that is fetching, a line that says "Checking the name is free…".
  • A card that is refetching while its old figure stays visible: a sm spinner in the header, and the number left where it is.
  • An icon-sized "busy" anywhere a CircularProgress would be too heavy — xs on a chip, sm beside body text, md in a row, lg in an empty card.
  • A named wait for a screen reader: label="Saving the menu" is what gets announced, politely, through role="status".

Not for

  • A button that is working → loading on Button, which grows its own spinner in and blocks the press.
  • A wait with a known amount → Progress, linear or circular, with a value.
  • A wait that owns the screen or a large panel → Progress's CircularProgress, or Splash Screen before the app has anything.
  • Content whose shape is known before it arrives → Skeleton, which holds the layout so nothing jumps when the data lands.
  • A text field that is validating → loading on Input.

Do

  • Name the wait: label="Syncing orders" rather than the default "Loading" when there are several on a screen.
  • Use tone="current" on a chip, on a filled surface or in coloured text, so the spinner is the colour of what it sits in.
  • Keep the old content on screen while it refreshes and put the spinner beside it. A blank card with a spinner in it throws away what the person was reading.
  • Match the size to the text next to it: xs with label-sm, sm with body-md.

Don't

  • Put one in a Button by hand; loading does it with the right size, the right colour and the press blocked.
  • Show a spinner for less than a few hundred milliseconds. A flash of one is noise — delay it, or skip it.
  • Stack several on one screen for one piece of work. One spinner per thing that is actually waiting.
  • Hide it from screen readers with aria-hidden unless something else on the page already announces the same wait.

Quick reference

tone
  • current
  • primary (default)
  • secondary
  • tertiary
  • destructive
  • success
  • warning
  • info
size
  • xs
  • sm
  • md (default)
  • lg

API

Spinner

A small busy indicator for inline use — at the end of a row, on a chip, beside a line of text, in the corner of a card that is refetching.

It is the indeterminate CircularProgress drawn lighter: the same arc turning over the same ring, on a thinner stroke with a faint track in the arc's own colour rather than a tonal container, so it sits in a line of text without outweighing it. Reach for CircularProgress when the wait deserves a place of its own on the screen, or when it has a value.

It announces itself politely — role="status" with an sr-only label, "Loading" unless you say otherwise — so a screen reader hears that something is happening without the visual being read out.

tone="current" takes the colour of the text around it, which is what a spinner on a chip or a coloured surface wants. A button that is working already has one: loading on Button.

import { Spinner } from 'omaris'
<div class="flex items-center gap-3">  <Text class="flex-1">orders-2026-09.csv</Text>  {#if syncing}<Spinner size="sm" label="Syncing orders" />{/if}</div>

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
No description in the source yet.
svg
The turning part. Reduced motion slows it rather than stopping it: a spinner that stands still says nothing is happening, which is a lie.
track
No description in the source yet.
indicator
No description in the source yet.
label
The sr-only text a screen reader announces.

Props

tone

Defaults to 'primary'

SpinnerTone

current inherits the surrounding text colour — the choice on a coloured surface.

current
The colour of the text around it.
primary
secondary
tertiary
destructive
success
warning
info
size

Defaults to 'md'

SpinnerSize

xs 12px, sm 16px, md 20px, lg 32px.

xs
12px — inside a chip, beside a caption.
sm
16px — beside body text, at the end of a dense row.
md
20px — at the end of a list row, in a toolbar.
lg
32px — in the middle of a card that is loading.
label

Defaults to 'Loading'

string

What a screen reader hears — "Saving", "Loading orders". Defaults to "Loading". It is sr-only; nothing is drawn.

class
string
classes
SpinnerClasses

Per-part Tailwind overrides. class still covers the root.