Let’s build a tab navigation with Alpine.js
Tags: tutorial, javascript, alpinejs, html
Published: 2020-10-19 21:34:18

If you already know what Alpine.js is, you can skip [to here](#what-are-we-going-to-build), otherwise let me introduce it to you.

From the [docs](https://github.com/alpinejs/alpine): Alpine.js is

>a rugged, minimal framework for composing JavaScript behavior in your markup.

I like to think of it as a lighter and lower cost Vue.js mixed with TailwindCSS. The syntax is very similar to Vue.js and you sprinkle it in your HTML just like TailwindCSS.


I found out about Alpine.js while following [Caleb Porzio](https://twitter.com/calebporzio), an active member of the Laravel community. At the time I checked out the Github link and to be honest didn’t care too much or was skeptical about it: “Ok, it’s just another JS framework, next week a new one comes out”.

Then I heard him talk about it in his podcast [No Plans To Merge](https://twitter.com/noplanstomerge) and it started to spark my interest. I took another look at the docs and was like: “well, I could just use Vue.js or plain JS, no need to do it with something else”. It was not until I heard him and [Adam Wathan](https://twitter.com/adamwathan) discuss it in the [Full Stack Radio](https://www.fullstackradio.com/episodes/132) podcast that I finally took Alpine.js for a ride.

In the episode, they discuss the use cases for the framework and it made sense to me. Here’s what made me want to use it: let’s say you are building a website that does not need too much JavaScript. There’s only one thing here and there, like a modal, a dropdown menu or tabs navigation. Of course, you could just write the JavaScript for that and don’t worry about it. It will be a bit of a pain to switch contexts and think about the imperative way of doing it with JS (get elements, toggling classes, etc…) or you could add Vue.js and create a component and all that. Seems a little overkill for just a simple interactive element. Or you could use Alpine.js and do it in a declarative way all in the HTML.

In the documentation, the author states that the framework’s syntax is entirely borrowed from Vue.js, so you will find things like `x-on:click`, `x-bind`, `x-show`, `x-for` which are basically the same as `v-on:click`, `v-bind` and so on. If you know Vue.js, it’s a breeze to learn Alpine.js, but even if you don’t, I think it is simple enough to pick up and understand the concepts.

## What are we going to build?

![Tab navigation screen recording](https://dev-to-uploads.s3.amazonaws.com/i/rctbwd1gvbl4kpuan23m.gif)

It's a simple tab navigation component. It's not the most exciting thing to do with JS, but it will be great to show you what Alpine.js can do. We are going to add TailwindCSS just so we don't have to worry too much about CSS and focus on Alpine's features. We get the framework form a CDN, so it's as easy as pasting the link in the HTML and be ready to go. You can go to [this Github repository](https://github.com/dberri/alpinejs-demo) to get the code and follow along, or use the develop branch to get the final result.

The initial HTML body looks like this:

```html
<div class="bg-gray-100 text-gray-800">
  <nav class="bg-gray-300">
    <ul class="flex">
      <li class="px-4 py-2 bg-gray-100">
        <a href="#option-1">Option 1</a>
      </li>
      <li class="px-4 py-2">
        <a href="#option-2">Option 2</a>
      </li>
      <li class="px-4 py-2">
        <a href="#option-3">Option 3</a>
      </li>
    </ul>
  </nav>

  <div class="p-4">
    <h2 class="text-xl mb-4">Here, you have the content of <strong>Option 1</strong></h2>
    <p class="mb-2">Qui ipsorum...</p>
  </div>

  <div class="p-4">
    <h2 class="text-xl mb-4">Here, <strong>Option 2</strong> contents are shown</h2>
    <p class="mb-2">Qui ipsorum...</p>
  </div>

  <div class="p-4">
    <h2 class="text-xl mb-4">And here, only <strong>Option 3</strong> contents should be displayed</h2>
    <p>Qui ipsorum...</p>
  </div>
</div>
```

If you open this with your browser, the styles will be there, but all the content (for all tabs) are shown at the same time. Now let's sprinkle some Alpine.js to only show a single tab's content:

```html
<div class="bg-gray-100 text-gray-800" x-data="{ selected: 'option-1' }">
  <nav class="bg-gray-300">
    <ul class="flex">
      <li class="px-4 py-2 bg-gray-100">
        <a href="#option-1" x-on:click="selected = 'option-1'">Option 1</a>
      </li>
      <li class="px-4 py-2">
        <a href="#option-2" x-on:click="selected = 'option-2'">Option 2</a>
      </li>
      <li class="px-4 py-2">
        <a href="#option-3" x-on:click="selected = 'option-3'">Option 3</a>
      </li>
    </ul>
  </nav>

    <div x-show="selected === 'option-1'" class="p-4">
      ...
    </div>

    <div x-show="selected === 'option-2'" class="p-4">
      ...
    </div>

    <div x-show="selected === 'option-3'" class="p-4">
      ...
    </div>
</div>
```

Here we are greeted by three directives: `x-data`, `x-on:click` and `x-show`.

`x-data` declares a new component scope. You can pass a JS object to it, and every element that is wrapped by the element which contains the `x-data` declaration, will have access to the JS object you passed to it. So, for example, if I had declared `x-data` in the nav element, only nav, ul, li and a would have access to that data. Since we want to use selected in the tab's content elements, we wrap everything with another element and use `x-data` there. You can have multiple `x-data` declarations in your page, just remember what each component has access to.

`x-on:click` attaches a click event listener to that element. The directive part is actually only `x-on`, the second part (click) is what event you want to listen to. You could for instance use `x-on:keydown`.escape to listen for presses on the escape key. Take a look at the docs to learn all the other events you can listen to. So, `x-on:click` can receive a function or an expression. Since we only want to change the state of selected, we can add this short expression directly, like `x-on:click="selected = 'option-1'"`. This will change the `selected` value in the `x-data` directive.

Finally, we have `x-show`. You can pass an expression that resolves to _true_ or _false_ and it will toggle the `display: none;` style on the element.

Ok, so with that, we should already have a basic tabs functionality:


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mmgqf6d48xgvi7eq0ew3.gif)


Wait, I mean, it's working but the tabs' styles are not changing to reflect their state. Well, let me present you `x-bind:class` (if you know Vue.js, this is not going to be a big surprise):

```html
<ul class="flex">
  <li class="px-4 py-2" x-bind:class="{ 'bg-gray-100': selected === 'option-1' }">
    <a href="#option-1" x-on:click="selected = 'option-1'">Option 1</a>
  </li>
  <li class="px-4 py-2" x-bind:class="{ 'bg-gray-100': selected === 'option-2' }">
    <a href="#option-2" x-on:click="selected = 'option-2'">Option 2</a>
  </li>
  <li class="px-4 py-2" x-bind:class="{ 'bg-gray-100': selected === 'option-3' }">
    <a href="#option-3" x-on:click="selected = 'option-3'">Option 3</a>
  </li>
</ul>
```

Here, we pass an object to the x-bind:class directive where the key is the class that should be applied and the value should be an expression that resolves to true or false. If it is true, the class is applied, else it is not. With that, this component is done.

Now let's add some fluff. First, just like Vue.js, we can use a shorter syntax, where `x-on` can be replaced by `@`, and `x-bind` can be replaced by `:`: 

```html
<li class="px-4 py-2" :class="{ 'bg-gray-100': selected === 'option-1' }">
  <a href="#option-1" @click="selected = 'option-1'">Option 1</a>
</li>
```

Now, for the _grand finale_ let’s add some transitions to the content boxes, so it’s easy on the eyes:

```html
<div x-show.transition.in.opacity.duration.750ms="selected === 'option-1'" class="p-4">
  ...
</div>

<div x-show.transition.in.opacity.duration.750ms="selected === 'option-2'" class="p-4">
  ...
</div>

<div x-show.transition.in.opacity.duration.750ms="selected === 'option-3'" class="p-4">
  ...
</div>
```

Yep, that’s it. It’s that easy. You can basically chain several options to make your transition look better. By default, if you only add `x-show.transition` it will add an _in and out opacity plus scale transition with 200ms of duration_. I changed that in this demo to only transition _in_, only change _opacity_ and to last _750ms_. If you want to take a look at other options, [check out the docs](https://github.com/alpinejs/alpine#x-show).

That’s it. With that little amount of code we were able to add tabs navigation to the application. Alpine.js is great when you want to make simple stuff like this, but it can also be used to create more complex interactions such as forms. In a future post I’ll show you how 😎.