Tutorial

Implementing a Tab Component from Scratch in Vanilla JavaScript

JavaScript

While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

Sharpen your development skills by recreating the well known tab component using nothing more then vanilla JavaScript and your unflappable wit!

On the surface, tabs may seem quite boring. We all know what they do and use them on a daily basis in our web browsers without giving them much thought, however, designing a modern, reusable tab component that works well on mobile devices using only native browser technologies can actually be quite challenging and is a great opportunity to build some “real world” code.

Base Requirements

Our tab component needs to be:

  • Responsive
  • Reusable
  • Nestable
  • Can set a default tab

Lets get to it! First I’ll start with the code, and then a break down what’s happening:

Markup

Here’s the basic HTML markup for our tab component:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Gator tabs</title>
  <link rel="stylesheet" href="index.css">
</head>
<body>
  <div class="gator-tabs-container">
    <ul class="gator-tabs-header">
      <li>The nest</li>
      <li class="default-gator-tab">Hello world!</li>
      <li>Rise of the gator</li>
    </ul>
    <ul class="gator-tabs">
      <li class="gator-tabs-container">
        <ul class="gator-tabs-header">
          <li>Nested 1</li>
          <li class="default-gator-tab">Nested 2</li>
        </ul>
        <ul class="gator-tabs">
          <li class="gator-tab">Some eggs in nest one</li>
          <li class="gator-tab">Some eggs in nest two</li>
        </ul>
      </li>
      <li class="gator-tab">Hello world from tab one!</li>
      <li class="gator-tab">Believe me I know tabs, I have the best tabs. Nobody does tabs like I do.</li>
      <li class="gator-tab">For now the eggs lay dormant but soon the gators will rise from the swamps.</li>
    </ul>
  </div>
  <script src="main.js"></script>
</body>
</html>

Styling

And some CSS styles to make it all look good:


  /* minimal reset */
  * {
    margin:0;
    border:0;
    padding:0;
    box-sizing:border-box;
    list-style:none;
  }

  body{
    background-color: #333;
    /* because serifs are gross (IMO) */
    font-family: sans-serif;
  }

  .gator-tabs-container{
    display:flex;
    flex-direction:column;
    width:100%;
  }

  .gator-tabs-header {
    background-color:#DFA612;
    display:flex;
    flex-wrap:wrap;
    padding:.375rem;
  }

  .gator-tabs-header > li {
    color:#fff;
    cursor:pointer;
    flex-grow:1;
    padding:.375rem;
    font-size:1.125rem;
  }

  .gator-tabs {
    display:flex;
  }

  .gator-tab {
    padding:1rem;
    color:#fff;
  }

JavaScript Code

And now let’s let the magic happen with some simple JavaScript:

function tabify( element ){
  const header = element.querySelector('.gator-tabs-header');
  const content = element.querySelector('.gator-tabs');
  const tab_headers = [...header.children];
  const tab_contents = [...content.children];
  tab_contents.forEach( x => x.style.display = 'none');
  let current_tab_index = -1;

  function setTab( index ){
    if( current_tab_index > -1 ){
      tab_headers[ current_tab_index ].style.fontWeight = 400;
      tab_contents[ current_tab_index ].style.display = 'none';
    }
    tab_headers[ index ].style.fontWeight = 800;
    tab_contents[ index ].style.display = 'flex';
    current_tab_index = index;
  }

  default_tab_index = tab_headers.findIndex( x => {
    return [...x.classList].indexOf('default-gator-tab') > -1;
  });

  default_tab_index = default_tab_index === -1 ? 0 : default_tab_index;
  setTab( default_tab_index );
  tab_headers.forEach((x,i) => x.onclick = event => setTab(i));
}

  // this is where the magic happens!
[...document.querySelectorAll('.gator-tabs-container')]
  .forEach(x => tabify(x));

Here’s a screenshot of the app on mobile:

Mobile screenshot of gator tabs

Recap/Breakdown

Our first requirement was to build something responsive and this was easily solved using Flexbox with the flex-wrap property so the tab headers will now stack on top of each other on mobile.

Making the code reusable may seem tricky at first but by wrapping our code into a function called tabify we can make anything into a tab that satisfied the required class/tag structure. This also makes our next requirement, nestable, fall into place automatically.

The last requirement is the ability to set a tab as the default selected when the page opens. This was accomplished by adding the default-gator-tab class to the desired tab header and our script will find the index of the first tab header with this class and use it to set the initial tab.

In a previous article, we described building tabs using React, but honestly, I’m hard pressed to find any significant advantages React has over plain ol’ Javascript in this instance. We’re able to pass any configuration to our script such as the default tab using class names (instead of props) and we are using about the same amount of code as the React version without the overhead of a framework!

If we wanted to, we could easily extend what we have to include animations or more complex feature but I’ll leave that up to you.

Creative Commons License