HextaUI

Spinner

Un indicateur de chargement avec des graduations façon Apple ou un anneau qui respire, pouvant attendre avant de s’afficher et rester assez longtemps pour ne pas clignoter.

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

Ajoute le composant, les tokens de thème HextaUI et les composants HextaUI dont il dépend.

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

Par défaut, c'est l'indicateur à huit branches des plateformes Apple. variant="ring" tourne pendant que son arc grandit et rétrécit, pour donner l'impression de travailler plutôt que d'être bloqué. Les deux sont dessinés dans la couleur de texte courante.

Sans scintillement

Passez loading et le spinner attend delay (150ms) avant de s'afficher, pour que les chargements rapides ne le fassent jamais clignoter, puis reste au moins minDuration (400ms) une fois 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>
  )
}

Tailles

sm, default, lg et xl pour les deux 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 ligne

À côté d'un texte, marquez le spinner aria-hidden pour que les mots parlent. Seul, il annonce son 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>
  )
}

Dans les boutons

Button et Command utilisent ce spinner pour leurs états de chargement, dimensionné sur les icônes du bouton.

"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>
  )
}
  • Seul, le spinner est un status nommé par label (« Loading »).
  • Avec aria-hidden, il perd son rôle, pour l'utiliser à côté d'un texte visible ou dans un bouton occupé.
  • Avec la réduction des animations, il pulse doucement au lieu de tourner, pour montrer qu'il se passe quelque chose.
PropTypePar défaut
variant
"ticks" | "ring""ticks"
sizenull laisse le parent décider de la taille.
"sm" | "default" | "lg" | "xl" | null"default"
label
string"Loading"
loadingActive l'affichage différé. Omettez-le pour toujours afficher.
boolean–
delay
number150
minDuration
number400
animatedMet l'animation en pause sans la masquer.
booleantrue
AttributDescription
data-slot="spinner"Le SVG, avec data-variant.
const visible = useDelayedLoading(isFetching, { delay: 150, minDuration: 400 })

Le même timing sous forme de hook, pour les skeletons, les overlays ou tout ce qui ne doit pas clignoter. Consultez le guide de useDelayedLoading.

Utilisé dans les blocks

Des blocks qui s’appuient sur Spinner.