Início / Componentes / Botões

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.