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).
<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:
On click of “See Alpine Resources”, we see the 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
Interested in Alpine.js?
Subscribe to Alpine.js Weekly. A free, once–weekly email roundup of Alpine.js news and articles