/ #alpinejs #javascript 

An accessible Alpine.js menu toggle

The “Hello World” of JavaScript frameworks and libraries seems to have become the todo app. In the case of Alpine.js a todo app is almost too large to showcase Alpine’s core benefits and use case. Another issue with a lot of JavaScript examples is that they forego accessibility. Therefore we won’t be building a todo app but an accessible navigation menu. Our menu is as follows (read on for a breakdown of how it’s built).

The “Hello World” of JavaScript frameworks and libraries seems to have become the todo app. In the case of Alpine.js a todo app is almost too large to showcase Alpine’s core benefits and use case.

Another issue with a lot of JavaScript examples is that they forego accessibility. Therefore we won’t be building a todo app but an accessible navigation menu.

Our menu is as follows (read on for a breakdown of how it’s built).

<nav
  aria-labelledby="nav-heading"
  x-data="{ isOpen: false }"
  :aria-expanded="isOpen"
>
  <h2 id="nav-heading">Alpine.js Accessible Navigation</h2>
  <button
    :aria-expanded="isOpen"
    aria-controls="nav-list"
    @click="isOpen = !isOpen"
  >
    See Alpine Resources
  </button>
  <ul :hidden="!isOpen" id="nav-list">
    <li>
      <a href="https://github.com/alpinejs/alpine">Alpine.js Docs</a>
    </li>
    <li>
      <a href="https://github.com/alpinejs/awesome-alpine"
        >Awesome Alpine.js list</a
      >
    </li>
    <li>
      <a href="https://alpinejs.codewithhugo.com/newsletter"
        >Alpine.js Weekly Newsletter</a
      >
    </li>
  </ul>
</nav>

The root element is a nav, Alpine.js state will get initialised to { isOpen: false } using x-data. Our menu contains a few links to Alpine.js resources.

<nav
  x-data="{ isOpen: false }"
>
  <ul>
    <li>
      <a href="https://github.com/alpinejs/alpine">Alpine.js Docs</a>
    </li>
    <li>
      <a href="https://github.com/alpinejs/awesome-alpine"
        >Awesome Alpine.js list</a
      >
    </li>
    <li>
      <a href="https://alpinejs.codewithhugo.com/newsletter"
        >Alpine.js Weekly Newsletter</a
      >
    </li>
  </ul>
</nav>

For accessibility purposes we bind aria-expanded to isOpen, this will mean the Alpine.js state of the nav will be reflected in the aria attribute. We also add a aria-labelledby whose value nav-heading is the id of our heading (h2).

<nav
  aria-labelledby="nav-heading"
  x-data="{ isOpen: false }"
  :aria-expanded="isOpen"
>
  <h2 id="nav-heading">Alpine.js Accessible Navigation</h2>
  <!-- rest of the component -->
</nav>

To implement our toggle, we use a button with a click event listener (@click) which flips the isOpen boolean field (it sets it to false if it was true and true if it was false).

For accessibility we bind aria-expanded on the button to isOpen and use aria-controls on the button to signal the relationship between the button and the ul. aria-controls is set to nav-list which is the id we’ll set on the ul.

<nav
  aria-labelledby="nav-heading"
  x-data="{ isOpen: false }"
  :aria-expanded="isOpen"
>
  <!-- rest of the component -->
  <button
    :aria-expanded="isOpen"
    aria-controls="nav-list"
    @click="isOpen = !isOpen"
  >
    See Alpine Resources
  </button>
  <ul id="nav-list">
    <!-- rest of the component -->
  </ul>
</nav>

Finally, since isOpen controls the visibility of our navigation list, we’ll bind the hidden attribute the nav-list/ul to !isOpen, we want the nav-list to be visible (not hidden) when open and be hidden when not open.

<nav
  aria-labelledby="nav-heading"
  x-data="{ isOpen: false }"
  :aria-expanded="isOpen"
>
  <!-- rest of the component -->
  <ul :hidden="!isOpen" id="nav-list">
    <!-- rest of the component -->
  </ul>
</nav>

The output of the component is as follows, on load, the nav-list is collapsed:

Collapsed Alpine.js Accessible Navigation, heading with a See Alpine Resources button underneath it

On click of “See Alpine Resources”, we see the 3 links.

Open Alpine.js Accessible Navigation, heading with a See Alpine Resources button underneath it followed by 3 links

That’s how you build an accessible navigation menu with Alpine.js.

You can find the examples for this post at Alpine.js Handbook Examples - 1.5 Accessible Menu

That’s it for this post, you can check out the Alpine.js tag on Code with Hugo for more in-depth Alpine.js guides.

If you’re interested in Alpine.js, Subscribe to Alpine.js Weekly. A free, once–weekly email roundup of Alpine.js news and articles.

Photo by Jordan Madrid on Unsplash

Author

Hugo Di Francesco

Co-author of "Professional JavaScript" with Packt. He runs the Code with Hugo website helping over 100,000 developers every month and holds an MEng in Mathematical Computation from University College London (UCL). He has used JavaScript extensively to create scalable and performant platforms at companies such as Canon and Elsevier.

Interested in Alpine.js?

Subscribe to Alpine.js Weekly. A free, once–weekly email roundup of Alpine.js news and articles