Menú lateral
La lista de navegación, y nada más: es la parte que se repite igual en todo proyecto. No hay componente ni JavaScript propio — son clases sobre enlaces. Qué va en ella, cómo se agrupa y dónde vive sigue siendo decisión tuya: fija en el lateral en escritorio, dentro de un panel lateral en el móvil, o en ambos.
Haz clic en los elementos
El elemento actual lleva color, fondo y peso en el primer nivel, y una píldora neutra dentro del submenú. Los grupos abren y cierran con la animación del acordeón.
En el móvil, dentro de un panel lateral
La misma lista en un panel lateral desde la izquierda — el patrón de menú de aplicación en pantalla pequeña.
Ejemplos
Las piezas del menú, una a una.
Elementos y elemento actual
is-active marca la página en la que está la persona.
Icono y contador
__icon a la izquierda; __count es la .tuc-badge is-plain, empujada a la derecha.
Secciones y separador
__section es la etiqueta de un bloque; __separator, una línea entre bloques.
Submenú
Un <details> con __sub; la flecha apunta a la derecha cerrado y hacia abajo abierto.
Cómo usar
Un <nav> con la clase y enlaces dentro. El data-tuc-accordion en el <nav>
solo hace falta cuando hay submenú: es lo que anima los <details>.
<nav class="tuc-menu" aria-label="Principal" data-tuc-accordion>
<!-- elemento simple -->
<a class="tuc-menu__item" href="/billing/">
<span class="tuc-menu__icon"><svg>…</svg></span>Facturación
</a>
<!-- elemento con contador -->
<a class="tuc-menu__item" href="/products/">
<span class="tuc-menu__icon"><svg>…</svg></span>Productos
<span class="tuc-badge is-plain tuc-menu__count">12</span>
</a>
<!-- elemento con submenú -->
<details open>
<summary class="tuc-menu__item">
<span class="tuc-menu__icon"><svg>…</svg></span>Rendimiento
</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/">Sucursales</a>
</div>
</details>
<hr class="tuc-menu__separator">
<div class="tuc-menu__section">Registros</div>
<a class="tuc-menu__item" href="/customers/">Clientes</a>
</nav>En la plantilla de Django
El elemento actual sale de la ruta resuelta. Escribe el menú una vez en un include y marca la página correcta en 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">Rendimiento</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 %}Deja abierto en la plantilla el grupo que contiene la página actual: quien llega por un enlace de submenú ya ve dónde está.
En el lateral y en el panel lateral
El menú no sabe dónde vive. En escritorio, ponlo en una columna fija; en el móvil, el mismo include va dentro de un
panel lateral desde la izquierda:
<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">Panel</h2>
</div>
<button type="button" class="tuc-btn is-ghost is-icon is-sm tuc-drawer__close" aria-label="Cerrar" 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 menú" data-tuc-drawer="#menu-main">…</button>En la columna fija del escritorio, conviértela en una columna flex con altura, con la marca arriba y el menú debajo. El
.tuc-menu ocupa el espacio que queda y se desplaza solo cuando la lista pasa de la pantalla — la marca se
queda fija arriba. En el panel lateral, quien se desplaza es su cuerpo, y no hace falta nada.
<aside style="display: flex; flex-direction: column; height: 100vh; position: sticky; top: 0">
<a href="/">Mi marca</a>
<nav class="tuc-menu" aria-label="Panel">…</nav>
</aside>Submenú
El elemento que abre y revela hijos es un <details>, y la apertura reutiliza el
acordeón: la animación, el teclado y el funcionamiento sin JavaScript ya vienen listos. El menú se ocupa solo
de la lectura — quién es hijo de quién, y dónde está el actual dentro del árbol.
El <summary> lleva tuc-menu__item y queda con el mismo aspecto que sus vecinos, con la etiqueta alineada a la
izquierda y la flecha empujada a la derecha. Los hijos van en tuc-menu__sub, sangrados y sujetos a un riel a la
izquierda. El hijo no repite icono: hereda el contexto del padre, y el punto delante marca la fila sin disputar atención
con la fila de iconos de arriba.
La flecha gira 90 grados, no 180
Derecha cuando está cerrado, hacia abajo cuando está abierto — así se lee un árbol de navegación: "hay algo aquí dentro" frente a "esta es la rama abierta". Y el riel a la izquierda existe porque la sangría sola se pierde en cuanto la lista pasa de cuatro o cinco filas.
El elemento actual lleva tres pistas
En el primer nivel, color de acento, fondo y peso — ninguna depende solo del color. Dentro del submenú el actual es una píldora neutra con el punto encendido: el color de acento queda para el nivel de arriba, y dos elementos naranjas en la misma columna dirían menos.
Teclado y accesibilidad
Son enlaces y <details>, y el teclado es el suyo: nada que aprender, nada que implementar.
| Tecla | Acción |
|---|---|
Tab Shift+Tab | Recorre los elementos, incluidos los títulos de submenú; los hijos de un grupo cerrado quedan fuera del recorrido |
Enter | Sigue el enlace, o abre y cierra el submenú enfocado |
Espacio | Abre y cierra el submenú enfocado |
Escribe aria-current="page" junto a is-active: la clase dibuja, el atributo es lo que el lector de pantalla
anuncia. Dale al <nav> un aria-label cuando haya más de una navegación en la página. Y no uses
role="menu" aquí: ese rol promete las flechas de un menú de aplicación, y esta lista es navegación común.
Clases
Solo clases — el menú no tiene JavaScript ni opciones. Lo que anima el submenú es el acordeón.
| Clase o atributo | Dónde | Para qué |
|---|---|---|
tuc-menu | <nav> | La lista, en columna |
tuc-menu__item | <a> o <summary> | Una fila del menú, con la altura de un control |
is-active | .tuc-menu__item | El elemento actual: resaltado en el primer nivel, píldora neutra en el submenú |
tuc-menu__icon | <span> en el elemento | Caja de 16px para el icono, en el color secundario; en el elemento activo, en el color de acento |
tuc-menu__count | .tuc-badge is-plain en el elemento | Empuja el contador a la derecha, con dígitos de ancho fijo |
tuc-menu__separator | <hr> | Línea entre bloques |
tuc-menu__section | <div> | Etiqueta de un bloque, en mayúsculas |
tuc-menu__sub | <div> dentro del <details> | Los hijos del submenú, sangrados y con riel |
data-tuc-accordion | <nav> | Anima la apertura de los submenús; data-single="true" deja uno abierto a la vez |