Skip to content

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

vue
<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.

vue
<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 -->
VarianteUsoRegra
primaryAção dominante do produto: "Enviar", "Salvar", "Continuar"1 por contexto (tela, modal, form)
detailCTA de marca: "Solicitar orçamento", "Fale conosco"Reserve para conversão; uso pontual
inversePeso máximo sem usar cor de brandBem em dialogs neutros
outlineAção secundária — voltar, recusar, ver maisPareie com primary em pares confirmar/cancelar
ghostAção terciária — cancelar, fechar, pularPadrão em toolbars e modais
linkNavegação textual, ações inline em parágrafosSe parece link, age como link
dangerDestruiçã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.

vue
<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 -->
SizeAlturaUso
xs28pxChips de filtro, ações em tabela densa, toolbars secundárias
sm36pxForms compactos, ações de linha em tabelas, dialogs densos
md44pxPadrão. Forms, diálogos, app shells. WCAG 2.5.5 hit target
lg52pxCTAs de seção em landing, checkout, ações principais
xl64pxHero, 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.

vue
<Button variant="primary" :loading="true">Salvando…</Button>
<Button variant="outline" :loading="true">Carregando…</Button>

loading aplica disabled, aria-busy="true" e color: transparent (mantém a largura — evita reflow). Use particípio passivo no texto quando a ação leva mais de 1s.

Desabilitado

vue
<Button :disabled="true">Desabilitado</Button>

Use disabled quando a ação é impossível (form inválido, pré-requisito faltando). Use loading quando está em andamento.


Com ícones

Ícones em SVG monocromático, stroke-width: 2, stroke-linecap: square. A cor herda via currentColor.

Ícone à esquerda (leading)

vue
<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)

vue
<!-- 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.

vue
<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.

vue
<Button :sharp="true">Geometria pura</Button>

Composições

Pares canônicos

vue
<!-- 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)

vue
<!-- Mobile, checkout, forms simples -->
<Button variant="primary" size="lg" :block="true">
  Continuar para pagamento
</Button>

Não use block em desktop com largura > 480px — sai proporcionalmente grande demais.


Props

PropTipoPadrãoDescrição
variant'primary' | 'detail' | 'inverse' | 'outline' | 'ghost' | 'link' | 'danger''primary'Hierarquia visual
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Tamanho do botão
disabledbooleanfalseDesabilita e aplica opacidade 0.4
loadingbooleanfalseDesabilita + aria-busy="true" + texto transparente
blockbooleanfalseOcupa 100% da largura do container
iconOnlybooleanfalseTorna o botão quadrado (sem padding lateral). Exige aria-label
sharpbooleanfalseRemove o radius (border-radius: 0)
type'button' | 'submit' | 'reset''button'Tipo do <button>. Sempre declare explicitamente

Events

EventoPayloadDescrição
clickMouseEventEmitido ao clicar (bloqueado quando disabled ou loading)

Acessibilidade

  • type="button" declarado explicitamente pelo componente — nunca depende do default do browser (submit).
  • aria-label obrigatório quando iconOnly. Nunca dependa do ícone visual para semântica.
  • aria-busy="true" aplicado automaticamente quando loading.
  • Hit target ≥ 44×44px em md+ (WCAG 2.5.5). xs/sm apenas 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.