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' 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.
<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.
<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.csvChecking the name is free…
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.
<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
smspinner in the header, and the number left where it is. - An icon-sized "busy" anywhere a
CircularProgresswould be too heavy —xson a chip,smbeside body text,mdin a row,lgin an empty card. - A named wait for a screen reader:
label="Saving the menu"is what gets announced, politely, throughrole="status".
Not for
- A button that is working →
loadingon 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 →
loadingon 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:
xswithlabel-sm,smwithbody-md.
Don't
- Put one in a
Buttonby hand;loadingdoes 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-hiddenunless something else on the page already announces the same wait.
Quick reference
tone currentprimary(default)secondarytertiarydestructivesuccesswarninginfo
size xssmmd(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-onlytext 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.
primarysecondarytertiarydestructivesuccesswarninginfo
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.