Tabs with HTML, SASS, and JS

Subscribe to my newsletter!

Idea

This Tutorial will make functional tabs with HTML, SASS, and JavaScript. We will make it so you can add the CSS and js file to the page and apply some classes to nested divs for it to work. We will also try to make it semantically correct by adding ARIA Attributes to the formal elements. However, don’t take this too seriously because I am unsure if I did it correctly. Still, it is always essential to give your HTML meaning.

Either way, Let us get started!

HTML of the Tabs

The Potential structure of such tabs could look something like this. We first would have a div with the class tabs and then two divs inside, one with the class tab-registers and one with the class tab-bodies. The First button inside tab-register is connected to the first item in the tab-bodies element. We can optionally give one of the buttons the class active-tab so it starts as the active tab.

<div class="tabs">
    <div class="tab-registers">
        <button>Tab 1</button>
        <button>Tab 2</button>
        <button>Tab 2</button>
    </div>
    <div class="tab-bodies">
        <div>
            ...
        </div>
        <div>
            ...
        </div>
        <div>
            ...
        </div>
    </div>
</div>

JavaScript of the Tabs

We start by getting an HTML collection of all elements with the tabs class, and we loop over it after we have turned it into a list using Array. from(list_like). The call back for the loop takes the element itself and the Index. We will need the Index to identify this tab group uniquely.

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

Then we save some of the essential elements to variables, namely the tabs-registers and tab-bodies elements.

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

Continuing, we get the active register, but because it could be undefined, we set it to the first button if that’s the case. Either way, we add the class active-tab to the element.

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

After that, we call the changeBody() function, which will, as it says, hide all bodies except the one corresponding to the current register/tab. We also call this function every time the tab is changed.

    changeBody()

Now we also loop over all the elements directly inside the registers container. These are the buttons/Tabs. We then set the aria-controls attribute, which says what other element is controlled by this one. We also have to set an id for the corresponding body. Use the tab id and the Index of the tab button to build a unique id.

Then we add an event listener to the button, remove the active tab class from the current register, and replace the content of the variable with the new current register, so we also need to add the active tab class. Last, we call the changeBody() function.

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

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

Now to the changeBody() function. In it, we once again loop over the registers children. We also get the Index of each iteration. Then if the body at that Index exists, we decide whether its display property is block or none depending on the current register. We do this check so there can be more registers than bodies. Then we also set the aria-expanded attribute for the button, which tells the user if the content of this panel or button that leads to a panel is expanded or not.

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

Sass of the Tabs

Below you see the sass/CSS code of the program. We won’t go into detail.

.tabs 
    font-family: 'Lucida Sans', sans-serif
    font-size: 20px


    .tab-registers 
        display: flex
        background-color: RGB(255, 255, 255)


    button 
        padding: 0.5em
        background-color: RGB(255, 255, 255)
        border: none
        font: inherit


    .tab-registers button:hover 
        cursor: pointer


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


    button.active-tab 
            background-color: rgb(235, 235, 235)

Showcase

Visit the Demo
Visit the Repository

Conclusion

Excellent! You have successfully created functional tabs using! See how you can add more features to this program, such as dynamically adding and deleting tabs.

Keep in mind that I am also just a beginner, so it could be that my way of solving these problems is not the best or that I use functions or features that aren’t advised. Always ask questions and try to solve problems your way!

Leave a Reply