Spinner
Indicador de carregamento acessível para esperas curtas de duração desconhecida.
Carregando agenda…
'use client';
import { Spinner } from '@/components/ui/spinner';
export default function SpinnerDemo() {
return (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Spinner label="Carregando agenda" />
Carregando agenda…
</div>
);
}Instalação
npx shadcn@latest add @lenstech/spinnerUso
import { Spinner } from "@/components/ui/spinner"<Spinner label="Carregando agenda" />O Spinner tem role="status" e é anunciado por leitores de tela com o texto de label (padrão: "Carregando…").
A cor segue o texto (currentColor), então basta uma classe text-* para mudá-la.
Exemplos
Tamanhos
'use client';
import { Spinner } from '@/components/ui/spinner';
export default function SpinnerSizes() {
return (
<div className="flex items-center gap-6">
<Spinner size="sm" />
<Spinner />
<Spinner size="lg" />
<Spinner size="xl" />
</div>
);
}Cores
Use apenas tokens semânticos, como text-muted-foreground ou text-link.
'use client';
import { Spinner } from '@/components/ui/spinner';
export default function SpinnerColors() {
return (
<div className="flex items-center gap-6">
<Spinner size="lg" />
<Spinner size="lg" className="text-muted-foreground" />
<Spinner size="lg" className="text-link" />
<Spinner size="lg" className="text-success" />
<Spinner size="lg" className="text-destructive" />
</div>
);
}Em botões
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
export default function SpinnerButton() {
const [pending, setPending] = useState(false);
return (
<Button
variant="outline"
isPending={pending}
onPress={() => {
setPending(true);
setTimeout(() => setPending(false), 2000);
}}
>
{({ isPending }) =>
isPending ? (
<>
{/* O Button já anuncia o estado pendente; o spinner aqui é só visual. */}
<Spinner decorative /> Emitindo nota…
</>
) : (
'Emitir nota fiscal'
)
}
</Button>
);
}Evite anúncio duplicado
O Button com isPending já anuncia o estado de carregamento. Dentro dele, torne o Spinner apenas visual com
aria-hidden e role="presentation", como no exemplo acima.
Sobre o conteúdo
Para indicar que um bloco está sendo atualizado, sobreponha o Spinner ao conteúdo atual.
Agendamentos de hoje
14
3 confirmações pendentes
'use client';
import { Spinner } from '@/components/ui/spinner';
export default function SpinnerOverlay() {
return (
<div className="relative w-full max-w-sm overflow-hidden rounded-xl border p-4" aria-busy="true">
<div className="grid gap-1 opacity-40">
<p className="text-sm text-muted-foreground">Agendamentos de hoje</p>
<p className="font-mono text-2xl font-semibold tabular-nums">14</p>
<p className="text-xs text-muted-foreground">3 confirmações pendentes</p>
</div>
<div className="absolute inset-0 flex items-center justify-center bg-background/60">
<Spinner size="lg" label="Atualizando agendamentos" />
</div>
</div>
);
}Quando usar
- Use para esperas curtas sem progresso mensurável: atualizar um card, enviar um formulário.
- Não use no carregamento inicial de listas e cards — prefira
Skeleton, que evita saltos de layout. - Não use quando o andamento é conhecido — use
Progress.
API
Spinner
Aceita todas as props de um <svg> (é o ícone Loader2 do lucide), mais:
Prop
Type