Lexington has been awarded a grant from Astro, to celebrate. Get a 30% discount. Apply code LEXINGTON30 ( uppercase ) at checkout.

← Back to all tutorials

How to create persistent tabs with Tailwind CSS and JavaScript

javascript-persistent-tabs
Published and written on Sep 02 2024 by Michael Andreuzza

Monday again, another day of tutorials! Today, we’ll be creating a persistent tabs component instead of using Alpine JS like we did on the past tutorial, we will be using JavaScript and Tailwind CSS.

Why Persistent Tabs?

Persistent tabs bring multiple advantages to web applications:

  • Enhanced User Experience: They retain the user’s last selected tab across page reloads or navigation, ensuring a seamless and consistent interface.
  • State Retention: Users maintain their position or context even after refreshing the page or returning later.
  • Increased Efficiency: Users save time and avoid frustration by not having to repeatedly select their preferred tab.
  • Customized Experience: They enable a more personalized user interface by remembering individual preferences.
  • Lowered Cognitive Load: Users don’t need to recall which tab they were on, which is especially helpful in complex applications with multiple sections.

Use Cases

Persistent tabs are beneficial in a variety of situations:

  • Dashboard Applications: Keep the last viewed section of a comprehensive dashboard.
  • E-commerce Platforms: Recall the last category a user was browsing in a product catalog.
  • Content Management Systems: Track the last edited section in a multi-step content creation process.
  • Social Media Platforms: Save the last viewed tab (e.g., posts, photos, about) on a user’s profile.
  • Educational Platforms: Retain the last accessed module or lesson in an online course.
  • Settings Pages: Remember the last settings category a user was editing.
  • Analytics Tools: Preserve the last viewed data set or time range chosen by the user.
  • Project Management Tools: Save the last project view (e.g., Kanban board, timeline, list view) selected by the user.
  • Documentation Websites: Maintain the last viewed section of multi-page documentation.
  • Multi-step Forms: Keep track of the last completed step if a user needs to return to the form later.

By integrating persistent tabs, you can significantly improve the usability and user-friendliness of your web application across these and many other scenarios.

Let’s write the markup

The wrapper

The wrapper is where we’ll add the snippet to persist the tab state. We’ll also add the id and class attributes to the wrapper.

  • id="tabsComponent": Assigns a unique ID to the wrapper. This ID will be used to target the wrapper in the JavaScript code.
<div id="tabsComponent">
    <!-- Tabs go here -->
</div>

The buttons container

The buttons are li elements without the need of classes on this example. We’ll add the role="presentation" attribute to the ul element to indicate that the element is a presentational element.

The buttons wrapper
  • role="presentation": Indicates that the element is a presentational element.
 <ul role="tablist">
     <!-- Tabs go here -->
 </ul>
The buttons

Attributes

  • role="tab": Indicates that the element is a tab.
  • id="tab-1": Assigns a unique ID to the tab.
  • aria-controls="panel-1": Associates the tab with its corresponding panel.
<button role="tab" id="tab-1" aria-controls="panel-1"> My account </button>
The panels

The panels are section elements where the content of the tabs will be displayed. Attributes

  • role="tabpanel": Indicates that the element is a tabpanel.
  • id="panel-1": Assigns a unique ID to the panel.
  • aria-labelledby="tab-1": Associates the panel with the corresponding tab.
<section role="tabpanel" id="panel-1" aria-labelledby="tab-1"> Content 1 </section>

We will do the same with the other panel, only that the seconde panel will have it’s own attributes.

Note: Classes are removed for brevity and you can get the full code on the GitHub repository.

<div id="tabsComponent">
  <!-- Tab List -->
  <ul role="tablist">
    <!-- Tab 1 -->
    <li role="presentation">
      <button role="tab" id="tab-1" aria-controls="panel-1"> My account </button>
    </li>
    <!-- Tab 2 -->
    <li role="presentation">
      <button role="tab" id="tab-2" aria-controls="panel-2"> Billing </button>
    </li>
  </ul>
  <!-- Panels -->
  <div>
    <!-- Panel 1 -->
    <section role="tabpanel" id="panel-1" aria-labelledby="tab-1"> Content 1 </section>
    <!-- Panel 2 -->
    <section role="tabpanel" id="panel-2" aria-labelledby="tab-2"> Content 2 </section>
  </div>
</div>

The script

The script is where we’ll define the logic for the tabs component.

The addEventListener

We’ll add the addEventListener function to the document object to listen for the DOMContentLoaded event.

  • const tabsComponent = document.getElementById("tabsComponent");: This line of code selects the wrapper element with the ID tabsComponent.
  • const tabButtons = tabsComponent.querySelectorAll(".tab-button");: This line of code selects all the buttons inside the wrapper.
  • const tabPanels = tabsComponent.querySelectorAll(".tab-panel");: This line of code selects all the panels inside the wrapper.
 const tabsComponent = document.getElementById("tabsComponent");
 const tabButtons = tabsComponent.querySelectorAll(".tab-button");
 const tabPanels = tabsComponent.querySelectorAll(".tab-panel");

The setActiveTab function

  • function setActiveTab(index): This function sets the active tab based on the index passed as an argument.
  • tabButtons.forEach((button, i) => {: This line of code iterates over each button and sets the aria-selected, tabindex, and class attributes based on the index passed as an argument.
  • button.setAttribute("aria-selected", i === index);: This line of code sets the aria-selected attribute to true if the index matches the current index.
  • button.setAttribute("tabindex", i === index ? "0" : "-1");: This line of code sets the tabindex attribute to 0 if the index matches the current index, and -1 otherwise.
  • button.classList.toggle("bg-orange-50", i === index);: This line of code toggles the bg-orange-50 class on the button if the index matches the current index.
  • button.classList.toggle("text-orange-600", i === index);: This line of code toggles the text-orange-600 class on the button if the index matches the current index.
tabButtons.forEach((button, i) => {
        button.setAttribute("aria-selected", i === index);
        button.setAttribute("tabindex", i === index ? "0" : "-1");
        button.classList.toggle("bg-orange-50", i === index);
        button.classList.toggle("text-orange-600", i === index);
      });

The tabPanels iteration

  • tabPanels.forEach((panel, i) => {: This line of code iterates over each panel and sets the display style based on the index passed as an argument.
  • panel.style.display = i === index ? "block" : "none";: This line of code sets the display style to block if the index matches the current index, and none otherwise.
 tabPanels.forEach((panel, i) => {
        panel.style.display = i === index ? "block" : "none";
      });

The localStorage

  • localStorage.setItem("activeTab", index.toString());: This line of code sets the activeTab key in the local storage to the index passed as an argument.
 localStorage.setItem("activeTab", index.toString());

The button click event listener

  • tabButtons.forEach((button, index) =>: This line of code iterates over each button and adds a click event listener to it.
  • button.addEventListener("click", () => setActiveTab(index));: This line of code adds a click event listener to each button and calls the setActiveTab function with the index of the button as an argument.
 tabButtons.forEach((button, index) => {
      button.addEventListener("click", () => setActiveTab(index));
    });

Setting the initial active tab

  • const storedActiveTab = parseInt(localStorage.getItem("activeTab")) || 0;: This line of code retrieves the value of the activeTab key in the local storage and parses it as an integer.
  • setActiveTab(storedActiveTab);: This line of code calls the setActiveTab function with the value of the activeTab key as an argument.
const storedActiveTab = parseInt(localStorage.getItem("activeTab")) || 0;
setActiveTab(storedActiveTab);

The complete script

 document.addEventListener("DOMContentLoaded", function () {
    const tabsComponent = document.getElementById("tabsComponent");
    const tabButtons = tabsComponent.querySelectorAll(".tab-button");
    const tabPanels = tabsComponent.querySelectorAll(".tab-panel");

    function setActiveTab(index) {
      tabButtons.forEach((button, i) => {
        button.setAttribute("aria-selected", i === index);
        button.setAttribute("tabindex", i === index ? "0" : "-1");
        button.classList.toggle("bg-orange-50", i === index);
        button.classList.toggle("text-orange-600", i === index);
      });

      tabPanels.forEach((panel, i) => {
        panel.style.display = i === index ? "block" : "none";
      });

      localStorage.setItem("activeTab", index.toString());
    }

    tabButtons.forEach((button, index) => {
      button.addEventListener("click", () => setActiveTab(index));
    });

    // Set initial active tab
    const storedActiveTab = parseInt(localStorage.getItem("activeTab")) || 0;
    setActiveTab(storedActiveTab);
  });

Conclusion

In this tutorial, we learned how to create a persistent tabs component using JavaScript and Tailwind CSS, and how to use JavaScript to iterate over elements and set attributes based on the index of the element. We also learned how to use the localStorage API to store and retrieve data in the browser.

I hope you found this tutorial helpful and have a great day!

/Michael Andreuzza

Did you like this tutorial? Please share it with your friends!

Get lifetime access to every theme available today for $199 and own them forever. Plus, new themes, lifetime updates, use on unlimited projects and enjoy lifetime support.

— No subscription required!