Button
Componente Button do shadcn - variantes, tamanhos, estados de carregamento e botões com ícones.
Busque em todas as páginas da documentação
Componente Button do shadcn - variantes, tamanhos, estados de carregamento e botões com ícones.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
npx shadcn@latest add buttonimport { Button } from "@/components/ui/button";
// Variantes
<Button variant="default">Primário</Button>
<Button variant="secondary">Secundário</Button>
<Button variant="destructive">Excluir</Button>
<Button variant="outline">Contorno</Button>
<Button variant="ghost">Fantasma</Button>
<Button variant="link">Link</Button>
// Tamanhos
<Button size="sm">Pequeno</Button>
<Button size="default">Padrão</Button>
<Button size="lg">Grande</Button>
<Button size="icon">Ícone</Button>
// Como um link
<Button asChild>
<a href="/sobre">Sobre</a>
</Button>
// Desabilitado
<Button disabled>Desabilitado</Button>Quando usar este componente: Para toda ação clicável em seu aplicativo - o componente Button oferece estilo consistente, acessibilidade e suporte a variantes.
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Loader2, Plus, Trash2, Download, ExternalLink } from "lucide-react";
export function ButtonShowcase() {
const [loading, setLoading] = useState(false);
async function handleClick() {
setLoading(true);
await new Promise((r) => setTimeout(r, 2000));
setLoading(false);
}
return (
<div className="space-y-6">
{/* Botão de carregamento */}
<div className="flex gap-3">
<Button onClick={handleClick} disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{loading ? "Salvando..." : "Salvar Alterações"}
</Button>
</div>
{/* Botões com ícone */}
<div className="flex gap-2">
<Button size="icon" variant="outline" aria-label="Adicionar item">
<Plus className="h-4 w-4" />
</Button>
<Button size="icon" variant="destructive" aria-label="Excluir item">
<Trash2 className="h-4 w-4" />
</Button>
</div>
{/* Botão com ícone e texto */}
<div className="flex gap-3">
<Button>
<Download className="mr-2 h-4 w-4" />
Download
</Button>
<Button variant="outline" asChild>
<a href="https://example.com" target="_blank" rel="noopener noreferrer">
Visitar Site
<ExternalLink className="ml-2 h-4 w-4" />
</a>
</Button>
</div>
{/* Grupo de botões */}
<div className="inline-flex rounded-md shadow-sm">
<Button variant="outline" className="rounded-r-none border-r-0">Esquerda</Button>
<Button variant="outline" className="rounded-none border-r-0">Centro</Button>
<Button variant="outline" className="rounded-l-none">Direita</Button>
</div>
{/* Largura total */}
<Button className="w-full" size="lg">
Botão de Largura Total
</Button>
</div>
);
}O que isso demonstra:
aria-label para acessibilidadeasChild para renderizar como uma tag de âncoraclass-variance-authority (cva) para definir combinações de variantes e tamanhosasChild usa o componente Slot do Radix para mesclar props no elemento filhocn() permite substituir qualquer classe padrão através da prop className<button> nativo por padrão, herdando todos os atributos de botão HTMLVariante personalizada via cva:
// components/ui/button.tsx - adicione uma variante personalizada
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
// ... outras variantes
success: "bg-green-600 text-white hover:bg-green-700",
warning: "bg-amber-500 text-white hover:bg-amber-600",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
xs: "h-7 rounded px-2 text-xs",
},
},
}
);Padrão de confirmação para exclusão:
function ConfirmDeleteButton({ onConfirm }: { onConfirm: () => void }) {
const [confirming, setConfirming] = useState(false);
if (confirming) {
return (
<div className="flex gap-2">
<Button variant="destructive" size="sm" onClick={onConfirm}>
Confirmar
</Button>
<Button variant="ghost" size="sm" onClick={() => setConfirming(false)}>
Cancelar
</Button>
</div>
);
}
return (
<Button variant="outline" size="sm" onClick={() => setConfirming(true)}>
<Trash2 className="mr-2 h-3 w-3" /> Excluir
</Button>
);
}Botão de envio com useFormStatus:
"use client";
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return (
<Button type="submit" disabled={pending}>
{pending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{pending ? "Enviando..." : "Enviar"}
</Button>
);
}// Tipo ButtonProps
import { Button, type ButtonProps } from "@/components/ui/button";
// Extração do tipo de variante
import { type VariantProps } from "class-variance-authority";
type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
// "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
// Polimórfico com asChild
<Button asChild>
<Link href="/about">Sobre</Link> {/* Link do Next.js */}
</Button>asChild remove o elemento button - O elemento filho recebe todas as props do button, mas não é envolvido por um <button>. Correção: Certifique-se de que o filho possa aceitar onClick, className e outras props de button.
Botões com ícone precisam de aria-label - Um botão apenas com ícone não tem texto visível. Correção: Sempre adicione aria-label para leitores de tela.
disabled impede todos os eventos - Ao contrário de aria-disabled, o atributo nativo disabled remove o botão da ordem de tabulação. Correção: Use aria-disabled se precisar que o botão permaneça focável enquanto inativo.
Botão dentro de um formulário envia por padrão - Botões sem type são type="submit" por padrão. Correção: Use type="button" para botões que não são de envio dentro de formulários.
Substituindo variantes - className="bg-red-500" em um botão variant="default" pode não ter precedência devido à especificidade. Correção: A utilidade cn() lida com isso através de tailwind-merge.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
<button> Nativo | Você precisa de um botão único sem o sistema de variantes | Você deseja estilo consistente em todo o aplicativo |
| Radix Toggle | Você precisa de um botão de alternância com estado pressionado | Um botão regular é suficiente |
<a> estilizado como botão | A ação navega para um URL | A ação aciona um evento do lado do cliente |
| Bibliotecas de botões com ícone | Você precisa de comportamento especializado de botão com ícone | O Button do shadcn com size="icon" cobre isso |
default - ações primárias (enviar, salvar)secondary - ações menos proeminentesdestructive - ações de exclusão ou perigosasoutline - ações com borda, de baixa ênfaseghost - estilo mínimo, frequentemente para barras de ferramentaslink - estilizado como um hiperlinkUse a prop asChild para delegar a renderização ao elemento filho:
<Button asChild>
<a href="/sobre">Sobre</a>
</Button><Button onClick={handleClick} disabled={loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{loading ? "Salvando..." : "Salvar Alterações"}
</Button>Slot do Radix para mesclar as props do Button em seu único elemento filho<button> é removido; o filho recebe onClick, className, etc.<a>, <Link>, ou qualquer elemento, mantendo o estilo do ButtonAdicione a variante à definição buttonVariants cva em components/ui/button.tsx:
success: "bg-green-600 text-white hover:bg-green-700",
warning: "bg-amber-500 text-white hover:bg-amber-600",class-variance-authority (cva) define combinações de variantes e tamanhoscn() (alimentada por tailwind-merge) mescla e deduplica classes Tailwindtype explícito são type="submit" por padrãotype="button" para botões que não são de envio dentro de formulários<div className="inline-flex rounded-md shadow-sm">
<Button variant="outline" className="rounded-r-none border-r-0">Esquerda</Button>
<Button variant="outline" className="rounded-none border-r-0">Centro</Button>
<Button variant="outline" className="rounded-l-none">Direita</Button>
</div>aria-label, a tecnologia assistiva anuncia o botão sem um nome significativoaria-label="Descrição" a botões size="icon"import { type VariantProps } from "class-variance-authority";
type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
// "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"function SubmitButton() {
const { pending } = useFormStatus();
return (
<Button type="submit" disabled={pending}>
{pending ? "Enviando..." : "Enviar"}
</Button>
);
}className definem a mesma propriedadecn() usa tailwind-merge para resolver isso, então deve funcionar -- mas apenas se você passar por cn()cn(), a última classe na folha de estilo vence, o que pode não ser sua substituiçãoRevisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥