Botões
Botão é a ação. A pergunta correta não é "como esse botão vai parecer?" — é "o que esse botão faz?". Antes de qualquer estilo, existe uma decisão semântica.
Semântica antes de estilo
Três elementos parecem botões. Só um é botão:
- <button> — ação. Submit, deletar, abrir modal, salvar.
- <a> — navegação. Muda a URL, abre nova página.
- <div>" — nenhum dos dois. Não tem foco por teclado, não tem role semântico, não ativa com Enter ou Space.
Quando você usa <div> como botão, você precisa adicionar manualmente tudo que <button> já oferece: tabindex, role="button", listeners de teclado. É trabalho extra para chegar no mesmo lugar.
/* Errado */
<div class="btn" onClick={handleClick}>Salvar</div>
/* Certo */
<button type="button" onClick={handleClick}>Salvar</button>
/* Também certo — para links que parecem botão */
<a href="/destino">Ir para destino</a>Hierarquia de ações
Nem toda ação tem o mesmo peso. A hierarquia visual comunica isso sem precisar de legenda:
- Primário: a ação principal da tela. Só um por seção. "Salvar", "Confirmar", "Criar".
- Secundário: ação alternativa de menor peso. "Editar", "Exportar".
- Ghost: ação de baixo peso, geralmente de saída. "Cancelar", "Fechar".
- Destrutivo: ação irreversível. Merece cor de aviso — vermelho — para comunicar risco antes do clique.
Estados
Todo botão tem estados além do padrão. Se você só projetou o estado default, você projetou 20% do botão:
- hover: feedback visual de que é clicável
- focus: obrigatório para navegação por teclado — precisa de ring visível
- active/pressed: feedback de que o clique foi registrado
- disabled:
opacity-50+cursor-not-allowed— mas o elemento ainda precisa estar no DOM para screen readers anunciarem que está desabilitado - loading: desabilite o botão durante a requisição, mostre feedback visual — spinner ou texto
A decisão
Neste guia, botões usam tokens semânticos diretamente. Sem classes utilitárias de cor hardcoded — var(--primary) e var(--primary-foreground) garantem que dark mode funciona sem variantes separadas.
/* HTML */
<button type="button" class="btn btn-primary">Ação primária</button>
<button type="button" class="btn btn-secondary">Secundário</button>
<button type="button" class="btn btn-ghost">Ghost</button>
<button type="button" class="btn btn-destructive">Deletar</button>/* CSS */
.btn {
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 500;
transition: opacity 0.2s;
}
.btn:hover {
opacity: 0.9;
}
.btn:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: var(--primary);
color: var(--primary-foreground);
}
.btn-secondary {
background: var(--surface);
color: var(--foreground);
border: 1px solid var(--border);
}
.btn-ghost {
background: transparent;
color: var(--foreground);
}
.btn-destructive {
background: var(--error);
color: white;
}O estado de loading adiciona o atributo disabled ao botão e mostra um indicador visual. O atributo aria-disabled pode ser usado para comunicar o estado para screen readers sem bloquear a interação.