Pestañas
La plantilla ya trae la lista dibujada, la pestaña inicial marcada y los demás paneles con hidden — la página
nace correcta antes del script. El JavaScript pone lo que el HTML no da: los roles que hacen que el lector de pantalla anuncie
"pestaña, 2 de 4", la conexión entre pestaña y panel y el teclado, en el que la lista es una sola parada del Tab.
Tres facturas pendientes. El panel es cualquier HTML, incluso el que llega por HTMX.
Ejemplos
El mismo marcado, con una clase o un atributo más.
Segmentadas
is-segmented tiene la altura de un campo, para cambiar la vista de una lista junto a un filtro.
128 contratos.
12 esperando pago.
116 liquidados.
Segmentadas junto a un campo
Borde y relleno suman la altura del control: la fila no se desalinea.
Activación manual
data-manual="true": las flechas solo mueven el foco, y Enter o Espacio abren.
Aquí iría el panel cargado por HTMX.
Una petición por pestaña abierta, no por pestaña recorrida.
Ningún adjunto.
Con icono y contador
El svg y la .tuc-badge is-plain dentro de la pestaña siguen su color.
Vista general del proyecto.
Siete tareas abiertas.
Preferencias del proyecto.
Muchas pestañas
La lista se desplaza en horizontal en lugar de partirse en dos líneas; la pestaña enfocada con las flechas entra en pantalla.
Enero.
Febrero.
Marzo.
Abril.
Mayo.
Junio.
Julio.
Agosto — la pestaña marcada en la plantilla.
Pestaña desactivada
disabled o aria-disabled="true": no se abre, y las flechas la saltan.
En edición.
Revisión.
Publicado.
Cómo usar
Escribe las clases, aria-selected="true" en la pestaña inicial y hidden en los demás paneles. Las
pestañas y los paneles se emparejan por orden: la tercera pestaña abre el tercer panel.
<div class="tuc-tabs" data-tuc-tabs>
<div class="tuc-tabs__list">
<button class="tuc-tabs__tab" aria-selected="true">Datos</button>
<button class="tuc-tabs__tab">Dirección</button>
</div>
<div class="tuc-tabs__panel">…</div>
<div class="tuc-tabs__panel" hidden>…</div>
</div>Sin aria-selected en ninguna, se abre la primera pestaña activa. El script da type="button" a las pestañas que no tienen
type — sin eso, cambiar de pestaña dentro de un <form> enviaría el formulario —, crea los
id que faltan y conecta pestaña y panel mediante aria-controls y aria-labelledby.
Por qué las clases vienen de la plantilla, y no del script
Así la lista ya nace dibujada en el primer fotograma, sin bloque de espera y sin salto cuando llega el JavaScript. Por el mismo motivo el hidden de los paneles es obligatorio: sin él, todos aparecerían apilados hasta que se ejecute el script.
Con HTMX, panel bajo demanda
Para no cargar el contenido de todas las pestañas a la vez, cada pestaña busca su propio panel la primera vez que se abre.
Activa data-manual="true": en el modo manual, abrir es siempre un clic — de ratón, de Enter o de
Espacio —, así que el hx-trigger en la pestaña cubre todos los caminos.
<div class="tuc-tabs" data-tuc-tabs data-manual="true">
<div class="tuc-tabs__list">
<button class="tuc-tabs__tab" aria-selected="true">Resumen</button>
<button class="tuc-tabs__tab" hx-get="{% url 'customer-invoices' customer.pk %}"
hx-target="#invoices" hx-trigger="click once">Facturas</button>
</div>
<div class="tuc-tabs__panel">{% include "customers/summary.html" %}</div>
<div class="tuc-tabs__panel" id="invoices" hidden></div>
</div>Por qué existe el modo manual
En la activación automática, la flecha cambia el panel al instante. Con paneles que cargan del servidor, recorrer cuatro pestañas hasta la deseada sería disparar cuatro peticiones. En el manual la flecha solo lleva el foco, y quien decide abrir es la persona.
En JavaScript
const tabs = new Tucano.Tabs('#customer', {
selected: 1, // índice de la pestaña inicial
manual: true,
onChange: (index, { tab, panel }) => console.log(index, tab.textContent),
});
tabs.select(2); // abre la tercera; dispara onChange
tabs.select(0, { silent: true }); // abre sin disparar nada
tabs.index; // índice de la pestaña abierta, o -1
tabs.tabs; // las pestañas, en orden
tabs.panels; // los paneles, en ordenCada cambio — por la persona o por select() — dispara tucano:change en el elemento de las pestañas, y el evento
burbujea. select() en una pestaña desactivada no hace nada.
document.addEventListener('tucano:change', (e) => {
if (!e.target.matches('.tuc-tabs')) return;
e.detail.value; // índice de la pestaña abierta
e.detail.tab; // el botón de la pestaña
e.detail.panel; // el panel
e.detail.instance; // las Tabs
});Las pestañas dentro de un panel son otro conjunto: el componente solo mira la lista y los paneles que son hijos directos de su elemento.
Teclado y accesibilidad
El teclado sigue el patrón de pestañas del ARIA APG. Toda la lista es una sola parada del Tab — si no, quien
navega con teclado recorrería todas las pestañas antes de llegar al contenido de la abierta.
| Tecla | Acción |
|---|---|
Tab | Entra en la pestaña abierta; el siguiente Tab va al contenido del panel |
← → | Pestaña anterior y siguiente, saltando las desactivadas y dando la vuelta en los extremos |
Home End | Primera y última pestaña activas |
Enter Espacio | En el modo manual, abre la pestaña enfocada |
La lista recibe role="tablist", cada pestaña role="tab" con aria-selected, y cada panel
role="tabpanel". El panel sin nada enfocable dentro recibe tabindex="0", para que el Tab llegue
al contenido; con un campo o un enlace dentro, no — sería una parada inútil antes de él.
API
Generada a partir del código en cada build — si algo no está aquí, no existe.
[data-tuc-tabs]new Tucano.Tabs(alvo, opcoes)data-manualselect destroytucano:changeOpciones
Las notas de esta tabla salen de los comentarios del código fuente, que están escritos en portugués.
| Opción | Por defecto | 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() |