HextaUI

Spinner

Un indicador de carga con marcas al estilo Apple o un anillo que respira, que puede esperar antes de mostrarse y permanecer el tiempo suficiente para no parpadear.

import { Spinner } from "@/components/ui/spinner"

export function SpinnerDemo() {
  return (
    <div className="flex items-center gap-10">
      <Spinner size="xl" />
      <Spinner size="xl" variant="ring" />
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/spinner.json

Añade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.

import { Spinner } from "@/components/ui/spinner"
<Spinner />
<Spinner variant="ring" size="lg" />
<Spinner loading={isFetching} />

El predeterminado es el indicador de ocho radios de las plataformas Apple. variant="ring" gira mientras su arco crece y se encoge, para que parezca que trabaja y no que está atascado. Ambos se dibujan con el color de texto actual.

Sin parpadeo

Pasa loading y el spinner espera delay (150ms) antes de mostrarse, para que las cargas rápidas nunca lo muestren de golpe, y luego permanece al menos minDuration (400ms) una vez visible.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"

export function SpinnerDelayed() {
  const [loading, setLoading] = React.useState(false)

  const load = (ms: number) => {
    setLoading(true)
    setTimeout(() => setLoading(false), ms)
  }

  return (
    <div className="flex max-w-full min-w-0 flex-col items-center gap-4">
      <div className="flex size-8 items-center justify-center">
        <Spinner loading={loading} size="lg" />
      </div>
      <div className="flex flex-wrap justify-center gap-2">
        <Button variant="outline" size="sm" onClick={() => load(100)}>
          Fast load (100ms)
        </Button>
        <Button variant="outline" size="sm" onClick={() => load(250)}>
          Load (250ms)
        </Button>
        <Button variant="outline" size="sm" onClick={() => load(2000)}>
          Slow load (2s)
        </Button>
      </div>
    </div>
  )
}

Tamaños

sm, default, lg y xl para ambas variantes.

import { Spinner } from "@/components/ui/spinner"

const sizes = ["sm", "default", "lg", "xl"] as const

export function SpinnerSizes() {
  return (
    <div className="flex flex-col gap-6">
      <div className="flex items-center gap-6">
        {sizes.map((size) => (
          <Spinner key={size} size={size} />
        ))}
      </div>
      <div className="flex items-center gap-6">
        {sizes.map((size) => (
          <Spinner key={size} size={size} variant="ring" />
        ))}
      </div>
    </div>
  )
}

En línea

Junto a texto, marca el spinner con aria-hidden para que hablen las palabras. Por sí solo, anuncia su label.

import { Badge } from "@/components/ui/badge"
import { Spinner } from "@/components/ui/spinner"

export function SpinnerInline() {
  return (
    <div className="flex flex-col items-start gap-4 text-sm">
      <p className="flex items-center gap-2 text-muted-foreground">
        <Spinner size="sm" aria-hidden />
        Saving changes…
      </p>
      <Badge>
        <Spinner variant="ring" aria-hidden />
        Deploying
      </Badge>
      <p className="flex items-center gap-2 text-primary">
        <Spinner size="sm" label="Syncing" />
        Spinners use the text color around them.
      </p>
    </div>
  )
}

En botones

Button y Command usan este spinner para sus estados de carga, dimensionado según los iconos del botón.

"use client"

import { IconRefresh } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

function save() {
  return new Promise((resolve) => setTimeout(resolve, 1600))
}

export function SpinnerButton() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button feedback loadingLabel="Saving…" onClick={save}>
        Save changes
      </Button>
      <Button
        variant="outline"
        size="icon"
        aria-label="Refresh"
        feedback
        onClick={save}
      >
        <IconRefresh />
      </Button>
    </div>
  )
}
  • Por sí solo, el spinner es un status con el nombre de label (“Loading”).
  • Con aria-hidden, pierde su rol, para usarlo junto a texto visible o dentro de un botón ocupado.
  • Con movimiento reducido, pulsa suavemente en lugar de girar, para seguir mostrando que algo está ocurriendo.
PropTipoPredeterminado
variant
"ticks" | "ring""ticks"
sizenull deja el tamaño en manos del padre.
"sm" | "default" | "lg" | "xl" | null"default"
label
string"Loading"
loadingActiva la visualización retrasada. Omítelo para mostrarlo siempre.
boolean–
delay
number150
minDuration
number400
animatedPausa la animación sin ocultarla.
booleantrue
AtributoDescripción
data-slot="spinner"El SVG, con data-variant.
const visible = useDelayedLoading(isFetching, { delay: 150, minDuration: 400 })

El mismo tiempo como hook, para skeletons, overlays o cualquier cosa que no deba aparecer de golpe. Consulta la guía de useDelayedLoading.

Usado en bloques

Bloques que se construyen sobre Spinner.