Spinner
BetaIndica una espera corta e indeterminada.
registry/ds/spinner.tsx Guardando…
import { Spinner } from "@/registry/ds/spinner";
export default function SpinnerBasic() {
return (
<div className="flex items-center gap-6">
<Spinner size="sm" />
<Spinner size="md" />
<Spinner size="lg" />
<Spinner size="xl" />
<span className="flex items-center gap-2 text-sm text-fg-muted">
<Spinner size="sm" className="text-icon-muted" label="Guardando" /> Guardando…
</span>
</div>
);
}Cuál indicador de carga
| Situación | Usá |
|---|---|
| Espera corta (< 10 s), no sabés cuánto falta. | Spinner |
| Sabés el porcentaje o los pasos. | Progress |
| Va a aparecer contenido con forma conocida (una lista, una card). | Skeleton |
| Un botón que espera su acción. | loading del Button (ya trae el spinner). |
- Aparece después de ~300 ms: para esperas más cortas, no muestres nada.
- Va donde va a aparecer el contenido, no en una esquina.
- Con texto al lado ("Guardando…") si la espera puede pasar de 3 s.
Dónde va
En línea con el texto que explica la espera; sobre el bloque que se recarga,
con aria-busy en el contenedor; o dentro del botón que lo causó, con la prop
loading de Button.
Verificando cupo en Física I…
Cálculo II
Comisión 2 · Lunes 8:00 · 12 lugares
"use client";
import { Button } from "@/registry/ds/button";
import { Card, CardBody, CardDescription, CardTitle } from "@/registry/ds/card";
import { Spinner } from "@/registry/ds/spinner";
/** Dónde va el spinner: en línea con el texto, tapando lo que se recarga (con aria-busy) y dentro del botón que lo causó */
export default function SpinnerContexts() {
return (
<div className="flex w-full max-w-lg flex-col gap-6">
<p className="flex items-center gap-2 text-sm text-fg-muted">
<Spinner size="sm" label="Verificando cupo" />
Verificando cupo en Física I…
</p>
<div className="relative" aria-busy="true">
<Card padding="sm" className="opacity-60">
<CardBody>
<CardTitle as="h4">Cálculo II</CardTitle>
<CardDescription>Comisión 2 · Lunes 8:00 · 12 lugares</CardDescription>
</CardBody>
</Card>
<div className="absolute inset-0 flex items-center justify-center">
<Spinner size="lg" label="Actualizando cupos" />
</div>
</div>
<div className="flex gap-3">
<Button loading>Inscribirme</Button>
<Button variant="stroke" loading>
Guardar
</Button>
</div>
</div>
);
}Medidas y tokens
| Size | Diámetro |
|---|---|
sm | 16 |
md | 20 |
lg | 24 |
xl | 32 |
Trazo 2.5. Pista al 25 % de opacidad + arco. Gira en 1 s; quieto con
prefers-reduced-motion.
| Parte | Propiedad | Token |
|---|---|---|
| Arco y pista | currentColor | primary/accent |
Hereda el color del texto: dentro de un Button Fill es blanco; pasale
className="text-icon-muted" para uno neutro.
Instalación
npx shadcn@latest add https://polen.cdc.cool/r/spinner.jsonPlayground
Configurá el componente y copiá el snippet: se genera del mismo estado que renderiza el preview.
size
<Spinner />| Prop | Tipo | Default | Descripción |
|---|---|---|---|
size | "sm" | "md" | "lg" | "xl" | "md" | 16 / 20 / 24 / 32. |
label | string | "Cargando" | Lo que anuncia el lector de pantalla. |
role="status" con aria-label: se anuncia una vez, sin interrumpir. Si el
spinner reemplaza contenido, marcá el contenedor con aria-busy="true".