Skip to main content

Spinner

Circular loading indicator for indeterminate or determinate progress.

Quick start
import { Spinner } from "mates-ui";

Spinner({ size: "md", color: "primary" });

Variants

import { html, renderApp } from 'mates';
import { Spinner, Text } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-6)" align="center" wrap>
${(["default","arc","double","segments","fade"]).map(v => html`
<x-col align="center" gap="var(--md-space-2)">
${Spinner({ variant: v, size: "md" })}
${Text(v, { type: "label-sm", color: "muted" })}
</x-col>
`)}
</x-row>
`;

renderApp(App, document.getElementById('app'));

Sizes

import { html, renderApp } from 'mates';
import { Spinner, Text } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-6)" align="center">
<x-col align="center" gap="var(--md-space-2)">
${Spinner({ size: "xs" })}
${Text("xs", { type: "label-sm", color: "muted" })}
</x-col>
<x-col align="center" gap="var(--md-space-2)">
${Spinner({ size: "sm" })}
${Text("sm", { type: "label-sm", color: "muted" })}
</x-col>
<x-col align="center" gap="var(--md-space-2)">
${Spinner({ size: "md" })}
${Text("md", { type: "label-sm", color: "muted" })}
</x-col>
<x-col align="center" gap="var(--md-space-2)">
${Spinner({ size: "lg" })}
${Text("lg", { type: "label-sm", color: "muted" })}
</x-col>
</x-row>
`;

renderApp(App, document.getElementById('app'));

Colors

import { html, renderApp } from 'mates';
import { Spinner, Text } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-6)" align="center">
<x-col align="center" gap="var(--md-space-2)">
${Spinner({ color: "primary" })}
${Text("primary", { type: "label-sm", color: "muted" })}
</x-col>
<x-col align="center" gap="var(--md-space-2)">
${Spinner({ color: "muted" })}
${Text("muted", { type: "label-sm", color: "muted" })}
</x-col>
<x-col align="center" gap="var(--md-space-2)">
${Spinner({ color: "error" })}
${Text("error", { type: "label-sm", color: "muted" })}
</x-col>
<x-col align="center" gap="var(--md-space-2)"
style="background:var(--md-color-primary);padding:var(--md-space-3);border-radius:var(--md-radius-md);">
${Spinner({ color: "white" })}
${Text("white", { type: "label-sm", style: { color: "rgba(255,255,255,0.7)" } })}
</x-col>
</x-row>
`;

renderApp(App, document.getElementById('app'));

Inside buttons

import { html, renderApp } from 'mates';
import { FilledButton, OutlinedButton, TextButton, DangerButton } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-3)" wrap>
${FilledButton("Saving…", { loading: true })}
${OutlinedButton("Loading", { loading: true })}
${TextButton("Wait", { loading: true })}
${DangerButton("Danger", { loading: true })}
</x-row>
`;

renderApp(App, document.getElementById('app'));

Inline usage

import { html, renderApp } from 'mates';
import { Spinner, Text } from 'mates-ui';

const App = () => () => html`
<x-col gap="var(--md-space-4)">
<x-row gap="var(--md-space-3)" align="center">
${Spinner({ size: "sm" })}
${Text("Fetching latest results…", { type: "body-md", color: "muted" })}
</x-row>
<x-row gap="var(--md-space-3)" align="center">
${Spinner({ size: "sm", color: "error" })}
${Text("Retrying connection…", { type: "body-md", color: "muted" })}
</x-row>
<x-row gap="var(--md-space-3)" align="center">
${Spinner({ size: "xs" })}
${Text("Syncing", { type: "label-md", color: "muted" })}
</x-row>
</x-col>
`;

renderApp(App, document.getElementById('app'));

Centered overlay

import { html, renderApp } from 'mates';
import { Spinner, Text } from 'mates-ui';

const App = () => () => html`
<div style="width:100%;max-width:28rem;height:10rem;
border:0.06rem dashed var(--md-color-border);
border-radius:var(--md-radius-lg);display:flex;flex-direction:column;
align-items:center;justify-content:center;gap:var(--md-space-3);
background:var(--md-color-surface-variant);">
${Spinner({ size: "lg" })}
${Text("Loading content…", { type: "label-md", color: "muted" })}
</div>
`;

renderApp(App, document.getElementById('app'));

Spinner

import { Spinner } from "mates-ui";
Prop Type Default Description
attr AttrMap Extra HTML attributes forwarded to the root element.
classes string | string[] Extra root classes — joined and appended after built-in classes.
color "primary" | "muted" | "error" | "white" "primary" Track color of the spinning arc.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
label string "Loading..." aria-label for accessibility.
on OnEventMap DOM event handlers on the root element.
size "xs" | "sm" | "md" | "lg" "md" Diameter of the spinner ring.
style StyleMap Root inline CSS (`style` prop).
style StyleMap Inline CSS on the root element.
testId string Selector for testing (data-testid attribute).
variant "default" | "arc" | "double" | "segments" | "fade" "default" Visual style. "default" = classic border arc; "arc" = 270° conic; "double" = counter-rotating rings; "segments" = dashed ring; "fade" = smooth gradient arc.

CSS tokens

Token Role
--md-color-error Error track color
--md-color-primary Primary track color
--md-color-text-muted Muted track color

Caveats

  • The arc, segments and fade variants use conic-gradient + CSS mask — they require a modern browser (Chrome 88+, Firefox 83+, Safari 15.4+).
  • Use `color: "white"` on dark/colored backgrounds only — it has no dark-mode adaptation.
  • Spinner does not accept children — pair it with a `Text` component for inline status messages.
  • `Button` has a built-in `loading` prop that renders a Spinner internally — prefer that over composing manually.