Spinner

A turning circle that says something is running, without saying how far (also called a loading indicator). Reach for it for short waits whose length nobody knows, beside a word that says what runs.

import { Spinner } from "@umriss-ui/core";

It takes the colour of the text around it and announces itself as “Loading”; name it better with aria-label where the word beside it is not enough.

Examples

With a word beside it

A spinner says only that something runs; the words beside it say what.

import { Spinner, Stack, Text } from "@umriss-ui/core";

export default function WithAWord() {
  return (
    <Stack direction="row" gap={2} align="center">
      <Spinner aria-label="Loading the tours" />
      <Text size="sm" tone="secondary">
        Loading the tours of North depot …
      </Text>
    </Stack>
  );
}

Sizes

size is the edge length in pixels; match it to the text it stands beside – 12 for small print, 14 for body text.

import { Spinner, Stack } from "@umriss-ui/core";

export default function Sizes() {
  return (
    <Stack direction="row" gap={4} align="center">
      <Spinner size={12} />
      <Spinner size={14} />
      <Spinner size={20} />
      <Spinner size={32} />
    </Stack>
  );
}

Colour

It draws in currentColor, so it takes the colour of whatever it stands in and needs no tone of its own.

import { Spinner, Stack, Text } from "@umriss-ui/core";

export default function Colour() {
  return (
    <Stack direction="row" gap={5} align="center" wrap>
      <Stack direction="row" gap={2} align="center">
        <Spinner size={14} />
        <Text size="sm">Refreshing</Text>
      </Stack>
      <Stack direction="row" gap={2} align="center" style={{ color: "var(--u-color-accent)" }}>
        <Spinner size={14} />
        <Text as="span" size="sm" style={{ color: "inherit" }}>
          Syncing the calendar
        </Text>
      </Stack>
    </Stack>
  );
}

Refresh a panel in place

While a panel reloads, the button that started it carries the spinner - loading on a Button - and the old figures stay readable below it. The button stays, so the head keeps its height.

import { useEffect, useState } from "react";
import { Button, Card, CardBody, CardHeader, Stack, Text } from "@umriss-ui/core";

export default function APanelRefreshing() {
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!loading) return;
    const timer = setTimeout(() => setLoading(false), 1500);
    return () => clearTimeout(timer);
  }, [loading]);

  return (
    <Card style={{ maxWidth: 420 }}>
      <CardHeader
        title="Open incidents"
        actions={
          <Button size="sm" variant="ghost" loading={loading} onClick={() => setLoading(true)}>
            Refresh
          </Button>
        }
      />
      <CardBody>
        <Stack gap={2}>
          <Text size="sm">INC-1048 · Checkout slow, card payments time out</Text>
          <Text size="sm">INC-1047 · Webhook deliveries delayed</Text>
        </Stack>
      </CardBody>
    </Card>
  );
}

When to use something else

Keyboard

The keys of Button apply here.

Accessibility

An svg with the role status, named "Loading" by the wording or by your aria-label; the role stays whatever you pass. It draws no text, so its name is all a screen reader hears of it. Where a word beside it already says what runs, as in a loading toast, hide it with aria-hidden.

With reduced motion it turns at less than half its speed instead of stopping, so it still says that something runs.

API

SpinnerProps

NameTypeDefaultDescription
sizenumber14Edge length in pixels.

Shown in: Sizes, Colour

refRef<SVGSVGElement>—Receives the component's root element. from RefProp

Shown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more

Also takes every attribute of <svg>.

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/core

Getting started

Customising

Layout

Typography

Actions

Forms

Choosing

Dates and times

Feedback

Overlays

Navigation

Data display

API index