ATOM
Components (registry)Indicators

Spinner

A spinning mark that says “still working” without blocking the page. Inline loads and button pending states.

Preview

Sizes: xssml (default: m)

Editorial

<!-- F12c editorial — non-derivable only. Review: Karen. -->

## Ejemplos

Inline pending state:

```tsx import { Spinner } from '@/components/atoms/Spinner';

<Spinner size="m" /> ```

## Accesibilidad

- Root already exposes `role="status"` and `aria-label="Loading"` — do not nest another live region around it. - Pair with disabled buttons or reserved layout so users know *what* is loading.

### Correcto

- role='status' permite que screen readers anuncien el loading state - aria-label='Loading' da texto accesible al spinner - currentColor adapta el contraste al contexto automaticamente - Reduced motion: spinner queda visible pero sin rotacion (aceptable por WCAG)

### Evitar

- No usar Spinner sin contexto visual o textual que indique que se esta cargando - No usar Spinner como decoracion — es un indicador funcional de estado - No cambiar la duracion a menos de 500ms — la rotacion se vuelve frenetica

## Cuándo no usar

- Full card/page content placeholders → `Skeleton`. - Blocking modal progress with a determinate bar → progress patterns, not an endless spinner alone.

## Criterio de uso

- Usa Spinner para trabajo breve e indeterminado en una zona concreta; el usuario debe poder identificar qué está esperando. - En botones, combina el spinner con estado disabled o loading para impedir reintentos accidentales. - Para operaciones largas o con progreso conocido, muestra porcentaje, etapas o una barra determinable en lugar de girar indefinidamente.

## Gotchas

- El componente ya expone `role="status"`; no lo envuelvas en otra live region que duplique el anuncio. - Un spinner aislado no explica el alcance de la espera: reserva espacio y conserva el contexto visible. - **Nota**: 12 lineas de CSS. Junto con Skeleton, los dos componentes mas ligeros del DS. El SVG inline es obligatorio — no se referencia externamente. - **Ojo**: prefers-reduced-motion: reduce pone animation-duration a 0ms — el spinner queda visible pero estatico. Esto es una excepcion aceptable segun WCAG: el spinner comunica estado esencial, pero la animacion no es esencial para entenderlo (el role='status' + aria-label ya comunican 'loading').

Uso

import { Spinner } from '@/components/atoms/Spinner';

<Spinner size="m" />

Props

PropTipoDefaultRango / opcionesWhatHow
sizeselectm`xs`, `s`, `m`, `l`Visual scale of the spinner mark.m inside buttons and default UI; xs/s for dense tables; l for page-level loading. Default: m.

Gotchas

  • a11y

    Root already exposes role=status and aria-label=Loading; do not nest another live region around it.

  • react

    Decorative only — pair with disabled buttons or skeleton content so users know what is loading.

Anatomía CSS

<div class="spinner">

</div>
ClasePropósito
spinnerroot
spinner--lmodifier
spinner--mmodifier
spinner--smodifier
spinner--xsmodifier

Tokens resueltos

Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.

VariantePropValorToken
allfgcurrentColor(literal)

Animaciones

PropiedadDuraciónEasing
@keyframes spinner-rotate

CSS mínimo funcional

Autocontenido: sin imports ni tokens. Para previews y prototipos — en producción se consume el CSS del DS (@atom-uikit/css/components.css</code>, <code>@atom-uikit/tokens/tokens.css).

/* -------------------------------------------------------------------------
   Spinner

   Loading indicator. Rotating circle with partial stroke.
   Sizes: xs, s, m, l. Inherits color from parent.

   Parts: .spinner
   ------------------------------------------------------------------------- */

.spinner {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: currentColor;
  animation: spinner-rotate 700ms linear infinite;
}

/* ---- Sizes ---- */

.spinner--xs { width: 0.875rem; height: 0.875rem; }
.spinner--s  { width: 1.125rem; height: 1.125rem; }
.spinner--m  { width: 1.5rem; height: 1.5rem; }
.spinner--l  { width: 2rem; height: 2rem; }

.spinner svg {
  width: 100%;
  height: 100%;
}

@keyframes spinner-rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* ---- Reduced motion ---- */

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation-duration: 0ms;
  }
}

Codigo fuente

components-react
components/atoms/Spinner.tsx
function cn(...classes: (string | false | undefined | null)[]) {
  return classes.filter(Boolean).join(' ');
}

export type SpinnerProps = {
  size?: 'xs' | 's' | 'm' | 'l';
  className?: string;
};

export function Spinner({ size = 'm', className }: SpinnerProps) {
  return (
    <span role="status" aria-label="Loading" className={cn('spinner', `spinner--${size}`, className)}>
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
        <path d="M12 2a10 10 0 0 1 10 10" />
      </svg>
    </span>
  );
}

Webflow

Webflowspinner

Pega en el Designer como application/json, luego convierte a Component (Atom / Spinner) y publica. Formato interno no documentado de Webflow — regenerable, no dependencia de runtime.

Setup del sitio (una vez)

Custom Code → Head:

<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/tokens.css">
<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/components.css">

Unsupported (no silencioso)

  • selector on .spinner svgcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • @media on (prefers-reduced-motion: reduce)not a Designer breakpoint — moved to head Custom Code block

Tras pegar: Create component → nombre Atom / Spinner → Publish.

Componentes relacionados

On this page

Detalles

Publicado14 de mayo de 2026
Categoriaindicators
Lectura...
Visitas...
Ayuda?Slack

Componente

Spinner

Source

components-react / cssDisponible via MCP: atom_uikit_source("spinner")
Abrir en Storybook