Sistema de pestañas en JavaScript y CSS: HTML, ARIA y estilos

Sistema de pestañas en JavaScript y CSS: HTML, ARIA y estilos

Este tutorial te permite crear pestañas funcionales con HTML, CSS y JavaScript. La idea es que puedas añadir los archivos CSS y JavaScript a la página y aplicar unas cuantas clases a los divs anidados para que todo funcione. También intentaremos que el marcado sea semánticamente correcto añadiendo atributos ARIA a los elementos que toca. Eso sí, no te lo tomes al pie de la letra, porque no estoy seguro de haberlo hecho del todo bien. En cualquier caso, dar sentido a tu HTML siempre merece la pena.

See the Pen
Onglets HTLM/CSS/JS
by Damien Flandrin (@dam62500)
on CodePen.0

El HTML de las pestañas ( tabs )

La estructura de estas pestañas podría ser algo así. Primero un div con la clase tabs y, dentro, otros dos divs: uno con la clase tab-registers y otro con la clase tab-bodies. El primer botón que hay dentro de tab-register está enlazado con el primer elemento de tab-bodies. A uno de los botones le podemos dar la clase active-tab para que sea la pestaña activa.

Pestañas HTML

html
<div class="tabs">
    <div class="tab-registers">
        <button class="active-tab">Tab 1</button>
        <button>Tab 2</button>
        <button>Tab 2</button>
    </div>
    <div class="tab-bodies">
        <div style="display:block;">
            Contenu Tab 1
        </div>
        <div style="display:none;">
            Contenu Tab 2
        </div>
        <div style="display:none;">
            Contenu Tab 3
        </div>
    </div>
</div>

El JavaScript de las pestañas ( tabs )

Empezamos obteniendo una colección HTML con todos los elementos que llevan la clase tabs y la recorremos en un bucle después de convertirla en lista con Array.from(list_like). El callback del bucle recibe el propio elemento y su índice. Necesitaremos ese índice para identificar cada grupo de pestañas de forma única.

Pestañas JavaScript

javascript
Array.from(document.querySelectorAll('.tabs')).forEach((tab_container, TabID) => {
    ...
})

Después guardamos en variables los elementos que nos interesan, es decir tabs-registers y tab-bodies.

Pestañas JavaScript

javascript
const registers = tab_container.querySelector('.tab-registers');
const bodies = tab_container.querySelector('.tab-bodies');

Seguimos: obtenemos el registro activo pero, como puede estar indefinido, en ese caso lo fijamos al primer botón. En cualquiera de los dos casos añadimos la clase active-tab al elemento.

Pestañas JavaScript

javascript
let activeRegister = registers.querySelector('.active-tab');
activeRegister = activeRegister ? activeRegister : registers.children[0]
activeRegister.classList.add('active-tab')

A continuación llamamos a la función changeBody() que, como su nombre indica, oculta todos los cuerpos salvo el que corresponde al registro, es decir a la pestaña actual. Llamamos a esa misma función cada vez que cambia la pestaña.

Ahora recorremos también todos los elementos que están directamente dentro del contenedor de registros: los botones, es decir, las propias pestañas. En cada uno definimos el atributo aria-controls, que indica qué otro elemento controla, y definimos el id correspondiente en el cuerpo asociado. Ese id se construye con el id del grupo de pestañas y el índice del botón, así que es único.

Después añadimos un event listener al botón: quitamos la clase de pestaña activa del registro actual, sustituimos el contenido de la variable por el nuevo registro actual y le añadimos a su vez la clase de pestaña activa. Por último, llamamos a la función changeBody().

Pestañas JavaScript

javascript
Array.from(registers.children).forEach((el, i) => {
  el.setAttribute('aria-controls', `${TabID}_${i}`)
  bodies.children[i]?.setAttribute('id', `${TabID}_${i}`)

el.addEventListener('click', (ev) => {
    let activeRegister = registers.querySelector('.active-tab');
    activeRegister.classList.remove('active-tab')
    activeRegister = el;
    activeRegister.classList.add('active-tab')
    changeBody(registers, bodies, activeRegister)
  })
})

Pasemos ahora a la función changeBody(). Dentro volvemos a recorrer los registros hijos y tomamos el índice de cada iteración. Si existe un cuerpo en ese índice, decidimos si su propiedad de visualización es block o none según cuál sea el registro actual. Hacemos esa comprobación porque puede haber más registros que cuerpos. Después definimos también el atributo aria-expanded del botón, que indica al usuario si el panel, o el botón que lleva a un panel, está desplegado o no.

Pestañas JavaScript

javascript
function changeBody() {
    Array.from(registers.children).forEach((el, i) => {
        if (bodies.children[i]) {
            bodies.children[i].style.display = el == activeRegister ? 'block' : 'none'
        }

el.setAttribute('aria-expanded', el == activeRegister ? 'true' : 'false')
    })
}

El CSS de las pestañas

Pestañas CSS

css
.tabs {
    font-family: 'Lucida Sans', sans-serif;
    font-size: 20px;
}

.tabs .tab-registers {
    display: flex;
    background-color: RGB(255, 255, 255);
}

.tabs button {
    padding: 0.5em;
    background-color: RGB(255, 255, 255);
    border: none;
    font: inherit;
}

.tabs .tab-registers button:hover {
    cursor: pointer;
}

.tabs .tab-bodies {
    padding: 0.5em;
    background-color: RGB(235, 235, 235);
    flex-grow: 1;
    overflow-y: auto;
}

.tabs button.active-tab {
    background-color: rgb(235, 235, 235);
}

CSSHTMLJavaScript

Damien Flandrin Desarrollador web desde 2010, creador de Gekkode y de Email Impact. Cada artículo se prueba en un proyecto real antes de publicarse. Contacto
Newsletter

Las nuevas pruebas, tutoriales y proyectos, por correo.

Pruebas reproducibles, código versionado, resultados fechados. Nunca spam.