Building Nav Tabs with Tailwind and Alpine.js Tutorial

Lately I’ve been focusing on Tailwind and Alpine.js a lot, mostly because they are so easy to prototype with and they make building functional frontends for Laravel backends a breeze. I couldn’t find a good example for how to make nav tabs work like they do in Bootstrap when I was first figuring out Alpine, so below is what I came up with.

Update: I recorded a video explaining how I built this, check it out here!

You can see a live demo of a similar tab example here.

Installation

The easiest way to install Alpine is to just include the script tag below in your HTML, but you can install it with NPM as well. I typically only install with NPM when using Laravel Mix for something like this blog.

<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>

We also need Tailwind, which I also just use a script tag for. If you are not prototyping, installing Tailwind with NPM is a better choice as it allows you to use tools such as PurgeCSS to optimize your site.

<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">

As a starting point, your HTML should look something like this, and the page should be blank.

Adding Tabs

I used the tabs from the Tailwind docs which can be found here. These are nice and simple while also being very similar to what Bootstrap offers. I took this example, and modified it slightly by adding padding (line 10 below), and remove the disabled tab button. In this example, I’ll be creating a 3 tab design, with different content in each tab.

But where will the actual content be placed? I’ve added a div with 3 inner divs starting on line 23. These divs contain the different text snippets we want to switch between using the corresponding tabs. The containing div has a w-full class added to it to make sure the tabs take the full width of the tabs’ nav bar. To check if the width is what you expect it be, you can open dev tools and check there or you can add a background color like bg-gray-100 that will make the size very obvious on the white background.

As you can see, all of the content divs show up, and that’s obviously going to happen because there is no interactivity built in (yet). The basic premise that this works on is simply hiding the tabbed content that we don’t want to see. This means keeping track of which tab is selected and only showing the active tab’s content.

Declaring a Component Scope

How do we track the opened tab? First we need to actually create a component. With Alpine, this is done by declaring a component scope on the HTML element that contains all parts of the component you’re building. This is all done inline, which is what makes Alpine.js so great for prototyping.

Defining the scope is done with x-data. When this is added to an HTML element, it allows you to define the data that will be available inside of your component. In our case, this is where we’ll keep track of the current tab. Below is an example of this without the Tailwind classes, so that it’s easier to understand.

Setting Data on Click

The value of openTab is now available to us inside of the component, and the default value is 1. You could use any type of index you prefer, and potentially for hash links it would make sense to use strings so the hash link can be pointed to a specific tag. A problem for another time! 😁

Our next issue is setting the value of openTab when a tab is clicked. We can do this by using @click.

Displaying the Correct Content

When a tab is clicked, the value of openTab will now be set to the value specified inside of @click. We can’t actually see that it’s being set, although we could check that by using our browser’s console. For the purpose of this tutorial, we’ll just continue to the next step where we show or hide the corresponding tabs.

To do this, we’ll use another Alpine.js directive called x-show. The way it works is by toggling display: none; depending on what the directive’s expression evaluates to. In our case, we want to evaluate whether the current tab is the tab that should be open by using the indexes we added to the click events.

Here is a working example with some added padding and updated tab titles:

Fixing Tab Styles

As you can see, the tabs work! When a tab is clicked, the correct tab content is displayed. But what about the styling of the tab buttons? When a tab is clicked and active, it should be shown as the active tab. This can be fixed by using :class to evaluate whether the current tab is active and adding the appropriate classes.

Here is the final version:

Refactoring

This code looks messy because of how many classes are used to style the tab elements, so it could benefit from a refactoring. More specifically, the active and inactive tab classes can be stored in the component data and then referenced. This would look something like this:

Conclusion

In the future I’ll be adding hash link support. That would basically check for a hash link on page load and assign it to the default tab index before the tab component loads (only if that tab index exists). I hope this is helpful! 😌

Leave a Comment