Button
Click target primário — seis variantes visuais e quatro presets de tamanho.
Overview
Button é o click target mais elementar do KDS. Toda interação que dispara
uma ação — submit de formulário, abrir um Dialog, confirmar um item da
lista — passa por ele. Internamente é um <button> estilizado por
class-variance-authority, com seis variantes visuais (default,
destructive, outline, secondary, ghost, link) e oito presets de
tamanho — quatro textuais (xs a lg) e quatro icon-only para botões
quadrados em toolbars e linhas de tabela.
A escolha da variante é uma escolha de hierarquia visual antes de ser
estética. Cada superfície carrega no máximo uma ação primária — o
default. Tudo mais é secundário (outline, secondary, ghost) ou
intencionalmente cauteloso (destructive).
Anatomy
<Button variant="default" size="default">
├─ [data-slot="button"]
├─ [data-variant="default"]
├─ [data-size="default"]
└─ children (texto + ícones lucide opcionais)
</Button>Atributos de dado (data-variant, data-size) estão expostos para CSS
custom downstream sem hack de classes. asChild permite renderizar como
um <a>, <Link> do Next, ou qualquer outro elemento — útil para que um
link se comporte visualmente como botão sem perder semântica de
navegação.
Usage
import { Button } from "@kalvner/kds/forms/button";
export function Example() {
return <Button onClick={() => save()}>Salvar</Button>;
}Props
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
variant | 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' | 'default' | Variante visual. |
size | 'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'icon-xs' | 'icon-sm' | 'icon-lg' | 'default' | Altura + padding. |
asChild | boolean | false | Renderiza o único filho em vez de um <button> (Radix Slot). |
disabled | boolean | false | Estado nativo disabled do botão. |
...rest | React.ComponentProps<'button'> | — | Tudo que <button> aceita (type, onClick, aria-*). |
Variants
States
Composition
Para ações de alto impacto — Excluir conta, Apagar workspace — não
basta um destructive: peça que o usuário digite o nome do recurso
em um Dialog antes de habilitar o botão. O Dialog ainda não está no KDS,
mas o padrão de stakes (low / high) ditará o gating.
When to use
- Disparar uma ação imediata (submit, salvar, abrir, navegar via JS).
- Apresentar uma escolha primária + alternativas (form footer, dialog).
- Comandos em toolbars — use
size="icon"comaria-labelclaro.
When not to use
- Para navegação pura entre páginas, prefira
<a>ou<Link>— só troque para<Button asChild>se precisar do estilo de botão na navegação. - Para alternar uma configuração de efeito imediato (notificações on/off),
use
Switch. - Para selecionar entre múltiplos valores, use
RadioGroupouSelect.
Best practices
- Uma ação primária por superfície. Se você está com dois botões
defaultno mesmo card, um deles deveria seroutlineoughost. - Ordem ocidental nos rodapés. Cancelar à esquerda, confirmar à direita. O olho percorre da esquerda para a direita até o commit.
- Destrutivo nunca é a escolha mais fácil. Posicione longe da ação primária, sempre confirme em um Dialog para itens críticos.
- Loading desabilita. Trocar texto por spinner sem desabilitar abre brecha para duplo envio.
- Não use cores fora do token. Botões coloridos out-of-token quebram
a hierarquia. Cite [[ui/colors/pure-white-black-avoid]] — nunca um
bg-whitepuro mesmo em contraste.
Accessibility
| Concern | Comportamento |
|---|---|
| Roles | <button> nativo. Quando asChild é usado, garanta que o componente filho gere semântica equivalente (<a> ganha role="link" automaticamente). |
| Keyboard | Tab navega; Enter e Space ativam. disabled remove do tab order. |
| Focus ring | focus-visible:ring-[3px] ring-ring/50 — visível só no foco via teclado. |
| Screen reader | Anunciado como "button" + texto interno. Para icon-only, exija aria-label. |
| Disabled vs aria-disabled | Use disabled (HTML nativo) — bloqueia clique e keyboard. aria-disabled apenas anuncia mas não bloqueia. |
| Loading state | Combine disabled + spinner. Considere aria-live="polite" no container quando o estado mudar dinamicamente. |
| Touch target | default é 36px de altura — borderline para mobile. Em telas táteis, prefira lg (40px) ou wraps com padding. |