DEV Community

Cover image for Creating powerful tabs with Alpine.js in less than 5 minutes
Mark Townsend
Mark Townsend

Posted on • Edited on

Creating powerful tabs with Alpine.js in less than 5 minutes

If you haven't heard of a little Javascript framework called, "Alpine.js" until now, you're about to discover something awesome.

Its creator, Caleb Porzio describes it in his own words:

Alpine.js offers you the reactive and declarative nature of big frameworks > like Vue or React at a much lower cost. You get to keep your DOM, and > sprinkle in behavior as you see fit.

Alpine.js is the definitive middle ground between vanilla Javascript and big frameworks. It effectively does away with the headache of having to write a bunch of event listeners and functions to handle your script functionality. It isn't meant to replace big robust frameworks like Vue and React. Instead, it's meant for those smaller use cases when you want to accomplish something smaller without having to fire up an entire framework.

Got it? Cool.

On to the point, then. Let's make a simple, yet robust tab system with Alpine.js!

To start, let's import alpine.js via the specified CDN. At the time of this post, alpine.js is currently at version 2.XX. Put the alpine script at the end of your head tag: <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>. Next, we'll write out some basic skeleton html. For our example, let's say we're coding a tab section for the product page of an e-commerce web site. We'll keep it simple with just a description and ratings tab.

<div id="tab_wrapper">
  <!-- The tabs navigation -->
  <nav>
    <a href="#">Description</a>
    <a href="#">Reviews</a>
  </nav>

  <!-- The tabs content -->
  <div>
    Lorem ipsum description.
  </div>
  <div>
    Lorem ipsum reviews.
   </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Basic stuff, a wrapper to hold the tab navigation and and content. Now, let's start sprinkling in alpine.js - you're going to love how easy the syntax is.

<div x-data="{ tab: 'description' }" id="tab_wrapper">
  <!-- The tabs navigation -->
  <nav>
    <a :class="{ 'active': tab === 'description' }" @click.prevent="tab = 'description'" href="#">Description</a>
    <a :class="{ 'active': tab === 'reviews' }" @click.prevent="tab = 'reviews'" href="#">Reviews</a>
  </nav>
...
Enter fullscreen mode Exit fullscreen mode

To start, we're strapping an x-data attribute onto the tab wrapper that contains all of our tab navigation and content. We're using a plain ol' Javascript object for its content. Defining a key of tab and giving it a string value of description. This is how we will tell our tab which one we want activate on the initial page load.

Next, we're strapping 2 attributes onto each navigation link. First, we're leveraging alpine's easy class handling and telling it that we want a class of active applied to the element any time the tab is equal to the string value representation of that tab. We want our users able to see which tab they're currently viewing. Next, we're essentially attaching a click event listener that says, "when this anchor tag is clicked, change the value of tab to X." Take note, we're adding .prevent to the click listener. That is the same as using preventDefault() inside the event listener. We do this just so we're not adding a hash to the url bar.

Let's wire up the tab content to make it actually work.

<div x-show="tab === 'description'">
   Lorem ipsum description.
</div>
<div x-show="tab === 'reviews'">
   Lorem ipsum reviews.
</div>
Enter fullscreen mode Exit fullscreen mode

Bam. That's all we needed to add to wire up our content to show when it is triggered. Crazy simple, right?! We didn't even have to write any custom CSS or apply classes. We're just telling the element to show itself when the value of tab matches its corresponding string value.

This will all work perfectly fine, but what if we wanted to make our tabs share-friendly? Naturally, if you were reading the reviews on the product and wanted to send the link to your friend, they would load the page and have the description tab open by default. What if we could make the tabs SEO and share friendly?

It's pretty easy to do using the browser's native anchor tags and it doesn't require changing very much of our code, either. Let's start by making the default tag dynamic based on the anchor tag in the url.

<div x-data="{ tab: window.location.hash ? window.location.hash.substring(1) : 'description' }" id="tab_wrapper">
...
Enter fullscreen mode Exit fullscreen mode

Since alpine.js works wonderfully with vanilla Javascript, we'll simply use Javascript's native window.location.hash to grab the hash out of the url. We'll approach it by using a ternary operator. If you're not familiar with what a ternary operator is, it's just a condensed if/else statement where the first part is the truth test (window.location.hash), the second part, which is followed by a ?, the if-true code, and the last part, followed by : which is the if-false code. tl;dr: if there's a hash, grab the hash and chop off the first character (.substring(1)) because we don't want the full hash (#description). Otherwise, simply default to the description tag.

Next, all we need to do is adjust the click event listener on each navigation link to push the tab hash into the url.

<nav>
   <a :class="{ 'active': tab === 'description' }" @click.prevent="tab = 'description'; window.location.hash = 'description'" href="#">Description</a>
   <a :class="{ 'active': tab === 'reviews' }" @click.prevent="tab = 'reviews'; window.location.hash = 'reviews'" href="#">Reviews</a>
</nav>
Enter fullscreen mode Exit fullscreen mode

That's all there is to it! Now our tabs will change the url bar and respect the anchor tag within the link. Super SEO and share friendly. And that's just scratching the surface of what alpine.js is capable of doing.

Here's the complete code:

<div x-data="{ tab: window.location.hash ? window.location.hash.substring(1) : 'description' }" id="tab_wrapper">
  <!-- The tabs navigation -->
  <nav>
    <a :class="{ 'active': tab === 'description' }" @click.prevent="tab = 'description'; window.location.hash = 'description'" href="#">Description</a>
    <a :class="{ 'active': tab === 'reviews' }" @click.prevent="tab = 'reviews'; window.location.hash = 'reviews'" href="#">Reviews</a>
  </nav>

  <!-- The tabs content -->
  <div x-show="tab === 'description'">
    Lorem ipsum description.
  </div>
  <div x-show="tab === 'reviews'">
    Lorem ipsum reviews.
   </div>

</div>
Enter fullscreen mode Exit fullscreen mode

One of the best things about alpine.js is that you can copy and paste this code several times on the same page and each section will continue functioning independently because the tab data is scoped to the wrapper container. This makes alpine.js extremely powerful and component-based.

Give it a try. I highly recommend you check out the full alpine.js documentation over on Github and consider using it in your next project.

Top comments (3)

Collapse
 
madurapa profile image
Maduka Jayalath

Thank you very much really helpful

Collapse
 
labibjamal profile image
Labib Muhammad Jamal

:)

Collapse
 
adonis2611 profile image
Nicholas Singh

can you do this same thing with svelte