BetaFeedback

Spinner

The wait, drawn. A circle whose arc turns until whatever is loading has loaded.

Overview

The wait, drawn. A circle whose arc turns until whatever is loading has loaded.

First example

<Spinner />
<Spinner variant="danger" size="sm" />
<Spinner variant="default" accessibilityLabel="Chargement des projets" />

Anatomy

<Spinner />

Two rings and no slots. The root is the track — the full circle in the variant's ink, at a fraction of its opacity — and its one child is the arc that turns over it: the same circle with a quarter missing. The two are one figure rather than two parts, so there is nothing between them for a slot to name.

Usage

<Spinner />
<Spinner variant="danger" size="sm" />
<Spinner variant="default" accessibilityLabel="Chargement des projets" />

Beside a label, in a row:

<Row gap={8} alignItems="center">
  <Spinner size="xs" variant="tertiary" />
  <Typography variant="body-sm">Synchronisation…</Typography>
</Row>

Frozen, for a screenshot or a test:

<Spinner animation={false} />

Root props

SpinnerProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
variantSpinnerVariant | undefinedNo
sizeSize | undefinedNoThe diameter, and the thickness that goes with it.
colorstring | undefinedNoA raw tint (`'#7c3aed'`), never a token (R7). There is one thing to colour on a spinner, so it lands on the ring — and on the track behind it, at the same fraction the variant's own ink is faded to.
animationboolean | undefinedNo`false` stops the rotation and mounts no worklet. The ring stays, so a list of rows switched off with `animation={false}` does not change height. A boolean rather than the `AnimationProp` union `PressableFeedback` takes: there is one animation here, so there is nothing for an object to switch off one at a time.
styleStyleProp<ViewStyle>No

Slots

This component is standalone and exposes no public slot.

Variants, sizes and colour

This component adds no visual axis of its own. The values it does take are in the generated types above and in the live demo.

Accessibility

accessibilityRole="progressbar" and accessibilityState={{ busy }} are set, and both stay overridable (R9). There is no default accessibilityLabel: only the caller knows what is loading, and "loading" alone is what the role already announces.

Migration from legacy

Legacyv1
<Indicator /><Spinner />
<Indicator themeColor="red" /><Spinner variant="danger" />
<Indicator size={32} /><Spinner size="lg" />

Implementation notes

Notes

No asChild

The one root in the library without one (R12). Slot merges a root's props into a single element, and that element keeps its own children — so a caller's element could become the track, but the arc is a second node it has no way to receive. Handing over the track alone would render a ring that never turns, silently.

Styling is the escape hatch instead: every ViewStyle key is a prop (R14), and style is still the last word.

Why borders and not an SVG stroke

The reference implementation draws one arc fading from opaque to 55%, which needs a linearGradient and therefore react-native-svg. That package is an optional peer here, and a component in the fifteen-component core cannot require one. Two circles of a single ink at two opacities read as the same figure, cost two views, and pull in nothing.

Button.Spinner is not this component with props

The button's spinner takes its diameter and its colour from the button's own recipe, which has already resolved both. Handing them to <Spinner size={…} color={…} /> would be R6 in reverse — a vocabulary prop carrying a computed number. What the two share is the turn, and that lives in useRotation.