Menu lateral
A lista de navegação, e só ela: é a parte que se repete igual em todo projeto. Não há componente nem JavaScript próprio — são classes sobre links. O que vai nela, como agrupa e onde mora continua sendo decisão sua: fixa na lateral no desktop, dentro de uma gaveta no celular, ou nas duas.
Clique nos itens
O item atual leva cor, fundo e peso no primeiro nível, e uma pílula neutra dentro do submenu. Os grupos abrem e fecham com a animação do acordeão.
No celular, dentro de uma gaveta
A mesma lista numa gaveta pela esquerda — o padrão de menu de sistema em tela pequena.
Exemplos
As peças do menu, uma de cada vez.
Itens e item atual
is-active marca a página em que a pessoa está.
Ícone e contador
__icon à esquerda; __count é a .tuc-badge is-plain, empurrada para a direita.
Seções e separador
__section é o rótulo de um bloco; __separator, uma linha entre blocos.
Submenu
Um <details> com __sub; a seta aponta para a direita fechado e para baixo aberto.
Como usar
Um <nav> com a classe e links dentro. O data-tuc-accordion no <nav>
só é preciso quando há submenu: é ele que anima os <details>.
<nav class="tuc-menu" aria-label="Principal" data-tuc-accordion>
<!-- item simples -->
<a class="tuc-menu__item" href="/billing/">
<span class="tuc-menu__icon"><svg>…</svg></span>Faturamento
</a>
<!-- item com contador -->
<a class="tuc-menu__item" href="/products/">
<span class="tuc-menu__icon"><svg>…</svg></span>Produtos
<span class="tuc-badge is-plain tuc-menu__count">12</span>
</a>
<!-- item com submenu -->
<details open>
<summary class="tuc-menu__item">
<span class="tuc-menu__icon"><svg>…</svg></span>Desempenho
</summary>
<div class="tuc-menu__sub">
<a class="tuc-menu__item is-active" href="/performance/period/" aria-current="page">Por período</a>
<a class="tuc-menu__item" href="/performance/branches/">Unidades</a>
</div>
</details>
<hr class="tuc-menu__separator">
<div class="tuc-menu__section">Cadastros</div>
<a class="tuc-menu__item" href="/customers/">Clientes</a>
</nav>No template do Django
O item atual sai da rota resolvida. Escreva o menu uma vez num include e ele marca a página certa em todas:
{% with route=request.resolver_match.url_name %}
<nav class="tuc-menu" aria-label="Principal" data-tuc-accordion>
<a class="tuc-menu__item{% if route == 'contract-list' %} is-active{% endif %}"
href="{% url 'contract-list' %}"{% if route == 'contract-list' %} aria-current="page"{% endif %}>
Contratos
</a>
<details{% if route|slice:":12" == 'performance-' %} open{% endif %}>
<summary class="tuc-menu__item">Desempenho</summary>
<div class="tuc-menu__sub">
<a class="tuc-menu__item{% if route == 'performance-period' %} is-active{% endif %}"
href="{% url 'performance-period' %}">Por período</a>
</div>
</details>
</nav>
{% endwith %}Deixe aberto no template o grupo que contém a página atual: quem chega por um link de submenu já vê onde está.
Na lateral e na gaveta
O menu não sabe onde mora. No desktop, ponha-o numa coluna fixa; no celular, o mesmo include vai dentro de uma
gaveta pela esquerda:
<dialog class="tuc-drawer is-left is-sm" id="menu-main" aria-labelledby="menu-main-title">
<div class="tuc-drawer__panel">
<div class="tuc-drawer__top">
<div class="tuc-drawer__header">
<h2 class="tuc-drawer__title" id="menu-main-title">Painel</h2>
</div>
<button type="button" class="tuc-btn is-ghost is-icon is-sm tuc-drawer__close" aria-label="Fechar" data-tuc-drawer-close>…</button>
</div>
<div class="tuc-drawer__body">
{% include "partials/menu.html" %}
</div>
</div>
</dialog>
<button class="tuc-btn is-outline is-icon" aria-label="Abrir menu" data-tuc-drawer="#menu-main">…</button>Na coluna fixa do desktop, faça dela uma coluna flex com altura, com a marca em cima e o menu embaixo. O
.tuc-menu ocupa o que sobra e rola sozinho quando a lista passa da tela — a marca continua parada no
topo. Na gaveta quem rola é o corpo dela, e não é preciso nada.
<aside style="display: flex; flex-direction: column; height: 100vh; position: sticky; top: 0">
<a href="/">Minha marca</a>
<nav class="tuc-menu" aria-label="Painel">…</nav>
</aside>Submenu
O item que abre e revela filhos é um <details>, e a abertura reaproveita o
acordeão: a animação, o teclado e o funcionamento sem JavaScript já vêm prontos. O menu cuida só
da leitura — quem é filho de quem, e onde está o atual dentro da árvore.
O <summary> leva tuc-menu__item e fica com a mesma cara dos vizinhos, com o rótulo alinhado à
esquerda e a seta empurrada para a direita. Os filhos ficam em tuc-menu__sub, recuados e presos a um trilho à
esquerda. Filho não repete ícone: ele herda o contexto do pai, e o ponto na frente marca a linha sem disputar atenção
com a fileira de ícones de cima.
A seta gira 90 graus, não 180
Direita quando fechado, para baixo quando aberto — é como uma árvore de navegação se lê: "há algo aqui dentro" contra "esta é a ramificação aberta". E o trilho à esquerda existe porque a indentação sozinha se perde assim que a lista passa de quatro ou cinco linhas.
O item atual leva três pistas
No primeiro nível, cor de destaque, fundo e peso — nenhuma depende só da cor. Dentro do submenu o atual é uma pílula neutra com o ponto aceso: a cor de destaque fica com o nível de cima, e dois itens laranja na mesma coluna diriam menos.
Teclado e acessibilidade
São links e <details>, e o teclado é o deles: nada a aprender, nada a implementar.
| Tecla | Ação |
|---|---|
Tab Shift+Tab | Anda entre os itens, incluindo os títulos de submenu; filhos de um grupo fechado ficam fora do caminho |
Enter | Segue o link, ou abre e fecha o submenu focado |
Espaço | Abre e fecha o submenu focado |
Escreva aria-current="page" junto do is-active: a classe desenha, o atributo é o que o leitor de tela
anuncia. Dê ao <nav> um aria-label quando houver mais de uma navegação na página. E não use
role="menu" aqui: esse papel promete as setas de um menu de aplicativo, e a lista é navegação comum.
Classes
Só classes — o menu não tem JavaScript nem opções. O que anima o submenu é o acordeão.
| Classe ou atributo | Onde | Para quê |
|---|---|---|
tuc-menu | <nav> | A lista, em coluna |
tuc-menu__item | <a> ou <summary> | Uma linha do menu, com a altura de um controle |
is-active | .tuc-menu__item | O item atual: destaque no primeiro nível, pílula neutra no submenu |
tuc-menu__icon | <span> no item | Caixa de 16px para o ícone, na cor de apoio; no item ativo, na cor de destaque |
tuc-menu__count | .tuc-badge is-plain no item | Empurra o contador para a direita, com dígitos de largura fixa |
tuc-menu__separator | <hr> | Linha entre blocos |
tuc-menu__section | <div> | Rótulo de um bloco, em caixa alta |
tuc-menu__sub | <div> dentro do <details> | Os filhos do submenu, recuados e com trilho |
data-tuc-accordion | <nav> | Anima a abertura dos submenus; data-single="true" deixa um aberto por vez |