Tabs bouwen met JavaScript en CSS, inclusief ARIA-attributen

Tabs bouwen met JavaScript en CSS, inclusief ARIA-attributen

Deze tutorial bouwt werkende tabs met HTML, CSS en JavaScript. Het idee is simpel: het CSS- en het JavaScript-bestand in de pagina zetten, een paar klassen aan geneste divs geven, klaar. Onderweg proberen we de markup semantisch netjes te houden en zetten we ARIA-attributen op de bijbehorende elementen. Neem dat deel niet te letterlijk, ik weet niet zeker of ik het helemaal juist heb gedaan. Hoe dan ook blijft het de moeite waard om je HTML betekenis te geven.

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

De HTML van de tabs

Zo kan de markup eruitzien. Een div met de klasse tabs, en daarin twee divs: een met de klasse tab-registers en een met de klasse tab-bodies. De eerste knop in tab-register hoort bij het eerste element in tab-bodies. Een van de knoppen kan de klasse active-tab krijgen en wordt daarmee de actieve tab.

HTML van de tabs

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>

Het JavaScript van de tabs

We beginnen met een HTML-collectie van alle elementen met de klasse tabs, die we met Array.from(list_like) in een lijst omzetten en vervolgens doorlopen. De callback van de lus krijgt het element zelf en de index binnen. Die index hebben we nodig om deze groep tabs uniek te kunnen aanduiden.

JavaScript van de tabs

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

Daarna zetten we een paar sleutelementen in variabelen, namelijk de elementen tabs-registers en tab-bodies.

JavaScript van de tabs

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

Vervolgens halen we het actieve register op. Dat kan undefined zijn, dus in dat geval vallen we terug op de eerste knop. In beide gevallen zetten we de klasse active-tab op het element.

JavaScript van de tabs

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

Daarna roepen we de functie changeBody() aan, die, zoals de naam zegt, alle bodies verbergt behalve die van het huidige register of tabblad. Diezelfde functie roepen we ook aan elke keer dat er van tab wordt gewisseld.

Nu doorlopen we ook alle elementen die direct in de registercontainer zitten. Dat zijn de knoppen, oftewel de tabs zelf. Op elke knop zetten we het attribuut aria-controls, dat aangeeft welk ander element erdoor wordt aangestuurd. Op de bijbehorende body moeten we dan ook een id zetten. Die id stellen we samen uit de id van de tabgroep en de index van de tabknop, zodat hij uniek is.

Daarna hangen we een event listener aan de knop: we halen de klasse van de actieve tab van het huidige register af, vervangen de inhoud van de variabele door het nieuwe huidige register en geven dat op zijn beurt die klasse. Tot slot roepen we changeBody() aan.

JavaScript van de tabs

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)
  })
})

Dan de functie changeBody() zelf. Daarin lopen we opnieuw over de onderliggende registers, telkens met de index van de iteratie erbij. Bestaat de body op die index, dan bepalen we aan de hand van het huidige register of zijn display-eigenschap block of none wordt. Die controle zit erin omdat er meer registers dan bodies kunnen zijn. Daarna zetten we op de knop nog het attribuut aria-expanded, dat de gebruiker vertelt of het paneel achter die knop uitgeklapt is of niet.

JavaScript van de tabs

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')
    })
}

De CSS van de tabs

CSS van de tabs

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 Webdeveloper sinds 2010, maker van Gekkode en Email Impact. Elk artikel wordt vóór publicatie getest op een echt project. Contact
Nieuwsbrief

Nieuwe tests, tutorials en projecten, per e-mail.

Reproduceerbare tests, geversioneerde code, gedateerde resultaten. Nooit spam.