Button
Bloco de ação do design system — 7 variantes, 5 tamanhos, loading, icon-only e composições.
Regras de design: radius 4px, hover muda só a cor (sem translate nem shadow), active scale(0.98) 80ms, 1 primary visível por contexto.
Uso
<script setup lang="ts">
import { Button } from '@halfbyte-labs/maka'
</script>
<template>
<Button variant="primary">Salvar alterações</Button>
</template>Variantes
Sete variantes com hierarquia clara. Use 1 primary por contexto — hierarquia vem da cor, não do tamanho.
<Button variant="primary">Primary</Button> <!-- azul · ação dominante -->
<Button variant="detail">Detail</Button> <!-- lime · CTA de marca -->
<Button variant="inverse">Inverse</Button> <!-- preto sólido -->
<Button variant="outline">Outline</Button> <!-- borda preta · secundário -->
<Button variant="ghost">Ghost</Button> <!-- sem fundo · terciário -->
<Button variant="link">Link →</Button> <!-- texto azul · inline -->
<Button variant="danger">Danger</Button> <!-- vermelho · destrutivo -->| Variante | Uso | Regra |
|---|---|---|
primary | Ação dominante do produto: "Enviar", "Salvar", "Continuar" | 1 por contexto (tela, modal, form) |
detail | CTA de marca: "Solicitar orçamento", "Fale conosco" | Reserve para conversão; uso pontual |
inverse | Peso máximo sem usar cor de brand | Bem em dialogs neutros |
outline | Ação secundária — voltar, recusar, ver mais | Pareie com primary em pares confirmar/cancelar |
ghost | Ação terciária — cancelar, fechar, pular | Padrão em toolbars e modais |
link | Navegação textual, ações inline em parágrafos | Se parece link, age como link |
danger | Destruição irreversível: "Excluir conta", "Apagar" | Sempre com confirmação adicional |
Tamanhos
Cinco tamanhos. Não misture mais de 2 na mesma tela — hierarquia vem da cor, não do tamanho.
<Button size="xs">Filtrar</Button> <!-- 28px · chips e tabelas densas -->
<Button size="sm">Salvar</Button> <!-- 36px · forms compactos -->
<Button size="md">Enviar</Button> <!-- 44px · padrão (WCAG 44×44) -->
<Button size="lg">Começar agora</Button> <!-- 52px · CTAs de landing -->
<Button size="xl">Vamos conversar</Button> <!-- 64px · hero, destaque máximo -->| Size | Altura | Uso |
|---|---|---|
xs | 28px | Chips de filtro, ações em tabela densa, toolbars secundárias |
sm | 36px | Forms compactos, ações de linha em tabelas, dialogs densos |
md | 44px | Padrão. Forms, diálogos, app shells. WCAG 2.5.5 hit target |
lg | 52px | CTAs de seção em landing, checkout, ações principais |
xl | 64px | Hero, splash, único botão da tela |
Estados
Loading
O botão fica desabilitado e anuncia aria-busy ao clicar. Combine com um Spinner no slot para feedback visual.
<Button variant="primary" :loading="true">Salvando…</Button>
<Button variant="outline" :loading="true">Carregando…</Button>
loadingaplicadisabled,aria-busy="true"ecolor: transparent(mantém a largura — evita reflow). Use particípio passivo no texto quando a ação leva mais de 1s.
Desabilitado
<Button :disabled="true">Desabilitado</Button>Use
disabledquando a ação é impossível (form inválido, pré-requisito faltando). Useloadingquando está em andamento.
Com ícones
Ícones em SVG monocromático, stroke-width: 2, stroke-linecap: square. A cor herda via currentColor.
Ícone à esquerda (leading)
<Button variant="primary">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M2 4l5 5 7-7" stroke="currentColor" stroke-width="2" stroke-linecap="square"/>
</svg>
Confirmar
</Button>Ícone à direita (trailing)
<!-- Setas de progressão sempre à direita -->
<Button variant="primary">
Continuar
<svg .../>
</Button>Icon-only
Use icon-only para botões sem texto. aria-label obrigatório — nunca dependa do ícone visual para semântica.
<Button variant="outline" :icon-only="true" aria-label="Editar">
<svg .../>
</Button>
<Button variant="ghost" :icon-only="true" aria-label="Mais opções">
<svg .../>
</Button>Forma
Sharp (radius 0)
Use :sharp="true" quando o botão coexiste com blocos quadrados — paginação, inputs, cards de bloco.
<Button :sharp="true">Geometria pura</Button>Composições
Pares canônicos
<!-- Confirmar / cancelar — ghost à esquerda, primary à direita -->
<Button variant="ghost">Cancelar</Button>
<Button variant="primary">Confirmar pedido</Button>
<!-- Salvar / descartar -->
<Button variant="outline">Descartar</Button>
<Button variant="primary">Salvar alterações</Button>
<!-- Destrutiva — danger nunca é o default -->
<Button variant="ghost">Cancelar</Button>
<Button variant="danger">Excluir conta</Button>
<!-- CTA dupla em hero -->
<Button variant="detail" size="lg">Começar projeto</Button>
<Button variant="ghost" size="lg">Ver cases →</Button>Block (full-width)
<!-- Mobile, checkout, forms simples -->
<Button variant="primary" size="lg" :block="true">
Continuar para pagamento
</Button>Não use
blockem desktop com largura > 480px — sai proporcionalmente grande demais.
Props
| Prop | Tipo | Padrão | Descrição |
|---|---|---|---|
variant | 'primary' | 'detail' | 'inverse' | 'outline' | 'ghost' | 'link' | 'danger' | 'primary' | Hierarquia visual |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Tamanho do botão |
disabled | boolean | false | Desabilita e aplica opacidade 0.4 |
loading | boolean | false | Desabilita + aria-busy="true" + texto transparente |
block | boolean | false | Ocupa 100% da largura do container |
iconOnly | boolean | false | Torna o botão quadrado (sem padding lateral). Exige aria-label |
sharp | boolean | false | Remove o radius (border-radius: 0) |
type | 'button' | 'submit' | 'reset' | 'button' | Tipo do <button>. Sempre declare explicitamente |
Events
| Evento | Payload | Descrição |
|---|---|---|
click | MouseEvent | Emitido ao clicar (bloqueado quando disabled ou loading) |
Acessibilidade
type="button"declarado explicitamente pelo componente — nunca depende do default do browser (submit).aria-labelobrigatório quandoiconOnly. Nunca dependa do ícone visual para semântica.aria-busy="true"aplicado automaticamente quandoloading.- Hit target ≥ 44×44px em
md+(WCAG 2.5.5).xs/smapenas em contextos densos onde o usuário tem precisão. - Foco visível nunca removido — outline 2px azul, offset 2px.
- Use
<Button>e nunca<div @click>. Você ganha teclado de graça.