Botão
Nasceu do que os componentes precisavam por dentro — o "Desfazer" do toast, o "Aplicar" do
calendário. Em vez de cada um inventar o seu, sai tudo daqui, e fica disponível para o seu projeto. É só
classe: um <button> continua sendo um <button>.
Exemplos
Variante, tamanho e estado são classes e atributos que se combinam.
Tamanhos
is-sm tem 30px, o padrão sai de --tuc-control-height e is-lg tem 44px.
Com ícone
O <svg> ganha o tamanho do botão sozinho. is-icon deixa quadrado — e aí o aria-label é obrigatório.
Ocupado
aria-busy="true" com .tuc-spinner. Clique em "Salvar" para ver a troca.
Desativado
disabled não recebe clique nem foco; aria-disabled="true" segue focável e anunciado.
Largura toda
is-block ocupa a linha — o botão de um formulário estreito ou de um celular.
Como link
Num <a> a classe tira o sublinhado; é o certo quando o botão navega, e não age.
Como usar
tuc-btn e uma variante. Sem variante o botão fica transparente, sem borda — a base de
que as variantes partem.
<button type="submit" class="tuc-btn is-primary">Salvar</button>
<button type="button" class="tuc-btn is-outline is-sm">Cancelar</button>
<button type="button" class="tuc-btn is-ghost is-icon" aria-label="Fechar">
<svg>...</svg>
</button>
<a class="tuc-btn is-link" href="/help/">Saiba mais</a>No formulário do Django
O botão é do template, não do form. Com type="submit" ele envia o formulário como sempre.
<form method="post">
{% csrf_token %}
{{ form }}
<a class="tuc-btn is-outline" href="{% url 'customers' %}">Cancelar</a>
<button type="submit" class="tuc-btn is-primary">Salvar</button>
</form>Em JavaScript
Não há componente para instanciar. O que se faz em código é trocar o estado — por exemplo, ocupado durante um envio:
const btn = document.querySelector('#save');
btn.setAttribute('aria-busy', 'true');
btn.disabled = true; // impede o segundo clique
btn.prepend(Object.assign(document.createElement('span'), { className: 'tuc-spinner' }));
// ...quando terminar
btn.removeAttribute('aria-busy');
btn.disabled = false;
btn.querySelector('.tuc-spinner').remove();Variantes e quando usar
Uma ação principal por área. As outras recuam em peso visual, e a destrutiva se distingue pela cor.
| Classe | Para quê |
|---|---|
is-primary | Ação principal, na cor de destaque (--tuc-accent) |
is-outline | Ação secundária, com borda |
is-ghost | Terciária, sem peso visual — ações dentro de tabela, barra de ferramentas |
is-danger | Destrutiva, pintada com --tuc-danger-fill |
is-link | Parece link, se comporta como botão |
Por que o vermelho do botão é outro token
--tuc-danger é mais claro no tema escuro, para ter contraste como texto — e um botão pintado com ele ficaria pastel com texto branco. --tuc-danger-fill é o mesmo nos dois temas.
Botão e campo têm a mesma altura
Os dois saem de --tuc-control-height — 38px, ou 44px no layout compacto, onde o alvo de toque precisa ser maior. Raio e borda vêm de --tuc-radius-md e --tuc-border-width. Um botão ao lado de um select fica alinhado sem ajuste.
Ocupado e desativado
Os dois impedem a ação, mas dizem coisas diferentes, e por isso têm desenhos diferentes.
<button class="tuc-btn is-primary" aria-busy="true" disabled>
<span class="tuc-spinner"></span> Salvando
</button>
<button class="tuc-btn is-outline" aria-disabled="true">Exportar</button>Ocupado não é desativado
O disabled junto do aria-busy impede o segundo clique, mas o esmaecido de 50% diria "não disponível" — e o botão está trabalhando. Com aria-busy="true" ele fica com a cor cheia e o cursor de espera. O spinner mede em em e pinta com currentColor: dentro do botão ele ocupa o lugar do ícone e sai branco num primário, sem variante.
Desativado mostra o cursor de bloqueio
Nada de pointer-events: none: sem receber o ponteiro o elemento não troca o cursor, e a pessoa fica sem sinal de que o botão não responde. Num <button disabled> o navegador já impede o clique. Com aria-disabled="true" o clique continua chegando de propósito — é o botão que segue focável e anunciado, e quem usa o atributo trata o clique no próprio manipulador.
Desativado também não reage ao hover — mudar de cor sugeriria que ainda funciona — e perde a sombra.
Ao pressionar, o botão encolhe de leve: num toque, sem hover, a cor sozinha não confirma que o dedo acertou
o alvo. Com prefers-reduced-motion esse movimento sai.
Teclado e acessibilidade
É o <button> do navegador: foco, Enter e Espaço vêm de graça.
O que cabe ao template é escolher o elemento certo e dar nome ao que não tem texto.
| Tecla | Ação |
|---|---|
Tab | Chega no botão, com o anel de foco dos campos; pula o disabled, não o aria-disabled |
Enter Espaço | Aciona o <button>; num <a class="tuc-btn">, só o Enter |
is-iconsem texto precisa dearia-label; o<svg>levaaria-hidden="true".<a>para navegar,<button>para agir — o leitor de tela anuncia um e outro de jeitos diferentes.- Dentro de
<form>, dêtype="button"ao que não envia: o padrão do navegador ésubmit.
Classes
Só CSS, sem JavaScript. Não escreva um segundo desenho de botão: ação dentro de tabela ou de bloco de
código também é .tuc-btn, com classe própria só para posicionar.
| Classe ou atributo | Para quê |
|---|---|
tuc-btn | A base: altura, fonte, raio, foco. Vale em <button> e <a> |
is-primary is-outline is-ghost is-danger is-link | Variante |
is-sm is-lg | Tamanho: 30px e 44px; o ícone acompanha |
is-icon | Quadrado, só ícone; combina com is-sm e is-lg |
is-block | Ocupa a largura toda |
disabled | Esmaecido, cursor de bloqueio, sem hover; o navegador barra o clique |
aria-disabled="true" | Mesmo desenho, mas focável e com o clique chegando |
aria-busy="true" | Ocupado: cor cheia e cursor de espera, mesmo com disabled |
.tuc-spinner dentro | Ocupa o lugar e o tamanho do ícone |
--tuc-btn-icon | Tamanho do <svg> filho; cada tamanho de botão define o seu |