Spinner
An animated loading indicator that stands in for an icon.
import { Spinner } from "@delacour/native-ui/spinner";
import { SPINNER_SIZES, Spinner } from "@delacour/native-ui/spinner";import { Text } from "@delacour/native-ui/text";import type { ReactElement } from "react";import { View } from "react-native";export function Demo(): ReactElement { return ( <View className="flex-row flex-wrap items-center gap-6"> {SPINNER_SIZES.map((size) => ( <View className="items-center gap-2" key={size}> <Spinner size={size} /> <Text.Caption size="xs">{size}</Text.Caption> </View> ))} <View className="items-center gap-2"> <Spinner size={40} /> <Text.Caption size="xs">40pt</Text.Caption> </View> </View> );}<Spinner />
<Spinner size="lg" color="danger" speed={0.7} />Sizes: the icon scale, shared outright — SPINNER_SIZES is ICON_SIZES, xs through 2xl
(14/16/18/20/24/32pt) — or an explicit number. A spinner stands in for an icon, so the two must
agree on what md means.
Colours: default, success, warning, danger, plus any token the theme emits
(primary-foreground, muted-foreground) or a literal (#EC4899).
A Tailwind palette name like emerald-500 only resolves if some utility class already pulled that
variable into the build — otherwise the token is unresolved and nothing is drawn. Prefer the
semantic tokens.
Size and colour are inherited

import { SPINNER_COLORS, Spinner } from "@delacour/native-ui/spinner";import { Text } from "@delacour/native-ui/text";import type { ReactElement } from "react";import { View } from "react-native";export function Demo(): ReactElement { return ( <View className="flex-row flex-wrap items-center gap-6"> {SPINNER_COLORS.map((color) => ( <View className="items-center gap-2" key={color}> <Spinner color={color} size="lg" /> <Text.Caption size="xs">{color}</Text.Caption> </View> ))} <View className="items-center gap-2"> <Spinner color="info" size="lg" /> <Text.Caption size="xs">info</Text.Caption> </View> <View className="items-center gap-2"> <Spinner color="#EC4899" size="lg" /> <Text.Caption size="xs">#EC4899</Text.Caption> </View> </View> );}Through exactly one path: the nearest IconDefaultsProvider, falling back to md on foreground.
A Button already wraps the spinner it composes in with its own icon class and variant foreground,
so the spinner does not read the button's context — a second path would recompute the same two
values and could drift from them.
An explicit size or color always wins.
Custom glyphs
Any child is the glyph, wrapped in a Spinner.Content automatically so it still rotates. Write
Spinner.Content out by hand only when the rotating layer itself needs styling.
speed belongs to the root and rides the spinner's context, so every part turns at one rate: 1
is one full turn per 900ms.
Layout
The root is the only sized box. Spinner.Content is size-full and the arc carries no width or
height at all — react-native-svg resolves both to '100%' when neither is set.
That size-full is load-bearing. Content-size the middle layer and the percentage resolves against
an indefinite parent, collapsing the glyph to zero.
The arc
The default glyph is drawn from SVG primitives, not a Central Icon — the set has no loader glyph. Rule 5 governs icons; primitives are fine.
Its caps are butt, and its round head is a separate Circle. The two half-rings share the
endpoint at the bottom of the ring; round caps there stack, and two semi-transparent discs
composite to roughly 0.8 alpha — a bright dot straddling the joint, opposite the head, at every
size. Do not put strokeLinecap="round" back.
The gradient stops are angle-compensated and run in user space. A linear gradient fades along its
axis, but a point at angle θ clockwise from the top sits at y = 12 - 10·cos θ — so two stops alone
make the fade stall near 3 and 9 o'clock and race through 12 and 6.
ReduceMotion.Never is deliberate
Under the default System policy withTiming completes instantly while the OS reduce-motion
setting is on, so withRepeat(-1) would spin a zero-length animation forever. A status indicator
is not decorative motion.
API
Prop
Type
Spinner.Content
The rotating layer itself. Written out by hand only when that layer needs styling — any child of
Spinner is wrapped in one automatically.
Prop
Type
size and color are inherited through exactly one path — the nearest IconDefaultsProvider —
falling back to md on foreground. A Button already wraps the spinner it composes in with its
own icon class and variant foreground, so the spinner does not read the button's context: a second
path would recompute the same two values and could drift from them.

