Abas
O template já traz a lista desenhada, a aba inicial marcada e os outros painéis com hidden — a página
nasce certa antes do script. O JavaScript põe o que o HTML não dá: os papéis que fazem o leitor de tela anunciar
"aba, 2 de 4", a ligação entre aba e painel e o teclado, em que a lista é uma parada só do Tab.
Três faturas em aberto. O painel é qualquer HTML, inclusive o que chega por HTMX.
Exemplos
A mesma marcação, com uma classe ou um atributo a mais.
Segmentadas
is-segmented tem a altura de um campo, para trocar a visão de uma lista ao lado de um filtro.
128 contratos.
12 aguardando pagamento.
116 quitados.
Segmentadas ao lado de um campo
Borda e respiro somam a altura do controle: a linha não desalinha.
Ativação manual
data-manual="true": as setas só movem o foco, e Enter ou Espaço abrem.
Aqui entraria o painel carregado por HTMX.
Uma requisição por aba aberta, não por aba atravessada.
Nenhum anexo.
Com ícone e contador
O svg e a .tuc-badge is-plain dentro da aba seguem a cor dela.
Visão geral do projeto.
Sete tarefas abertas.
Preferências do projeto.
Muitas abas
A lista rola na horizontal em vez de quebrar em duas linhas; a aba focada pelas setas entra na tela.
Janeiro.
Fevereiro.
Março.
Abril.
Maio.
Junho.
Julho.
Agosto — a aba marcada no template.
Aba desativada
disabled ou aria-disabled="true": não abre, e as setas pulam por ela.
Em edição.
Revisão.
No ar.
Como usar
Escreva as classes, aria-selected="true" na aba inicial e hidden nos outros painéis. As
abas e os painéis casam pela ordem: a terceira aba abre o terceiro painel.
<div class="tuc-tabs" data-tuc-tabs>
<div class="tuc-tabs__list">
<button class="tuc-tabs__tab" aria-selected="true">Dados</button>
<button class="tuc-tabs__tab">Endereço</button>
</div>
<div class="tuc-tabs__panel">…</div>
<div class="tuc-tabs__panel" hidden>…</div>
</div>Sem aria-selected em nenhuma, abre a primeira aba ativa. O script dá type="button" às abas que não têm
type — sem isso, trocar de aba dentro de um <form> enviaria o formulário —, cria os
id que faltam e liga aba e painel por aria-controls e aria-labelledby.
Por que as classes vêm do template, e não do script
Assim a lista já nasce desenhada no primeiro quadro, sem bloco de espera e sem pulo quando o JavaScript chega. Pelo mesmo motivo o hidden dos painéis é obrigatório: sem ele, todos apareceriam empilhados até o script rodar.
Com HTMX, painel sob demanda
Para não carregar o conteúdo de todas as abas de uma vez, cada aba busca o próprio painel na primeira vez que é aberta.
Ligue data-manual="true": no modo manual, abrir é sempre um clique — de mouse, de Enter ou de
Espaço —, então o hx-trigger na aba cobre todos os caminhos.
<div class="tuc-tabs" data-tuc-tabs data-manual="true">
<div class="tuc-tabs__list">
<button class="tuc-tabs__tab" aria-selected="true">Resumo</button>
<button class="tuc-tabs__tab" hx-get="{% url 'customer-invoices' customer.pk %}"
hx-target="#invoices" hx-trigger="click once">Faturas</button>
</div>
<div class="tuc-tabs__panel">{% include "customers/summary.html" %}</div>
<div class="tuc-tabs__panel" id="invoices" hidden></div>
</div>Por que o manual existe
Na ativação automática, a seta troca o painel no mesmo instante. Com painel que carrega do servidor, atravessar quatro abas até a desejada seria disparar quatro requisições. No manual a seta só leva o foco, e quem decide abrir é a pessoa.
Em JavaScript
const tabs = new Tucano.Tabs('#customer', {
selected: 1, // índice da aba inicial
manual: true,
onChange: (index, { tab, panel }) => console.log(index, tab.textContent),
});
tabs.select(2); // abre a terceira; dispara onChange
tabs.select(0, { silent: true }); // abre sem disparar nada
tabs.index; // índice da aba aberta, ou -1
tabs.tabs; // as abas, na ordem
tabs.panels; // os painéis, na ordemCada troca — pela pessoa ou por select() — dispara tucano:change no elemento das abas, e o evento
borbulha. select() numa aba desativada não faz nada.
document.addEventListener('tucano:change', (e) => {
if (!e.target.matches('.tuc-tabs')) return;
e.detail.value; // índice da aba aberta
e.detail.tab; // o botão da aba
e.detail.panel; // o painel
e.detail.instance; // as Tabs
});Abas dentro de um painel são outro conjunto: o componente só olha a lista e os painéis que são filhos diretos do seu elemento.
Teclado e acessibilidade
O teclado segue o padrão de abas do ARIA APG. A lista inteira é uma parada só do Tab — senão quem
navega por teclado atravessaria todas as abas antes de chegar ao conteúdo da aberta.
| Tecla | Ação |
|---|---|
Tab | Entra na aba aberta; o próximo Tab vai ao conteúdo do painel |
← → | Aba anterior e próxima, pulando as desativadas e dando a volta nas pontas |
Home End | Primeira e última aba ativas |
Enter Espaço | No modo manual, abre a aba focada |
A lista recebe role="tablist", cada aba role="tab" com aria-selected, e cada painel
role="tabpanel". O painel sem nada focável dentro ganha tabindex="0", para o Tab chegar
ao conteúdo; com um campo ou link dentro, não — seria uma parada inútil antes dele.
API
Gerada do código a cada build — se algo não está aqui, não existe.
[data-tuc-tabs]new Tucano.Tabs(alvo, opcoes)data-manualselect destroytucano:changeOpções
| Opção | Padrão | Para quê |
|---|---|---|
selected | null | indice da aba inicial; sem ele vale a marcada com aria-selected="true", ou a primeira |
manual | false | setas so movem o foco, e Enter ou Espaco trocam o painel — para painel que carrega por HTMX |
onChange | null | (index, detail) a cada troca feita pela pessoa ou por select() |