Creating powerful tabs with Alpine.js in less than 5 minutes
Tags: alpinejs, tabs
Published: 2020-05-30 02:02:41

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](https://twitter.com/calebporzio) 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.

```html
<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>
```
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.

```html
<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>
...
```
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.

```html
<div x-show="tab === 'description'">
   Lorem ipsum description.
</div>
<div x-show="tab === 'reviews'">
   Lorem ipsum reviews.
</div>
```
*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.

```html
<div x-data="{ tab: window.location.hash ? window.location.hash.substring(1) : 'description' }" id="tab_wrapper">
...
```
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.

```html
<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>
```
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:

```html
<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>
```

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](https://github.com/alpinejs/alpine) and consider using it in your next project.