How to toggle a mobile menu with Tailwind and AlpineJS

David Carr

Javascript Development HTML

Table of Contents

Making a mobile and desktop menu is something that is super simple to do with Tailwind and AlpineJS.

A typical navbar will contain a series of links styled with Tailwind, it's possible to apply different styles depending on the browser size for mobile and desktop, but to me it's far simpler to have 2 divs in your HTML. A div for mobile and one for desktop.

Here's an HTML snippet for both the menus

<nav class="bg-blue-700 dark:bg-gray-900 shadow mb-10">

    <div class="flex py-4 max-w-7xl mx-auto px-2 sm:px-4 lg:px-8">
        <button class="lg:hidden text-gray-100 hover:text-gray-300 rounded-md transition duration-150 ease-in-out mr-1" aria-label="Main menu" aria-expanded="false">

            <svg class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
            </svg>

        </button>

        <a href='#' class="text-sm text-white hover:text-gray-300">Logo</a>

        <div class="flex flex-grow justify-between">

                <div class="flex flex-grow justify-between">

                    <div class="hidden lg:ml-6 lg:flex">
                        <a href="#" class="ml-8 px-1 text-sm text-white hover:text-gray-300">Blog</a>
                        <a href="#" class="ml-8 px-1 text-sm text-white hover:text-gray-300">About Me</a>
                        <a href="#" class="ml-8 px-1 text-sm text-white hover:text-gray-300">Packages</a>
                        <a href="#" class="ml-8 px-1 text-sm text-white hover:text-gray-300">Books</a>
                        <a href="#" class="ml-8 px-1 text-sm text-white hover:text-gray-300">Contact</a>
                    </div>

                </div>

            </div>
    </div>

    <div>
        <a href="#" class="block pl-3 py-2 font-medium text-white hover:text-gray-700 hover:bg-white">About Me</a>
        <a href="#" class="block pl-3 py-2 font-medium text-white hover:text-gray-700 hover:bg-white">Packages</a>
        <a href="#" class="block pl-3 py-2 font-medium text-white hover:text-gray-700 hover:bg-white">Books</a>
        <a href="#" class="block pl-3 py-2 font-medium text-white hover:text-gray-700 hover:bg-white">Contact</a>
    </div>

</nav>

This looks like this:

Both menus are displayed, as this is only using HTML with Tailwind styling.

There is a button icon that only shows on screens smaller than Tailwind's LG breakpoint

<button class="lg:hidden text-gray-100 hover:text-gray-300 rounded-md transition duration-150 ease-in-out mr-1" aria-label="Main menu" aria-expanded="false">

    <svg class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
    </svg>

</button>

What we want to happen is when on mobile or less than LG breakpoint, then show the icon and when clicked show the mobile menu.

Tailwind already takes care of hiding the main menu using hidden lg:flex which tells Tailwind to hide the main nav by default and use flex on LG breakpoints.

Using AlpineJS

To make the menu interactive, we can use AlpineJS, first on the root nav element create an inline variable called open with a value of false. By using x-data="{}" alpine can set "state" or "variables"

<nav x-data="{ open: false }"

Now on the icon when it shows thanks to Tailwind we can react to a click event and set open to true.

<svg @click="open = true"

To use a variable defined in Alpine we can use x-show to show only when open is set to true

In addition if a user clicks anywhere else outside of the menu then close the menu

@click.away="open = false"

Apply this to a div.

<div x-cloak x-show="open" @click.away="open = false">

That's all we need.

In summary set a variable called open on the main nav and then x-show on the mobile div and an @click event to the mobile icon to trigger showing/hiding the menu.

Updating the HTML code:

<nav x-data="{ open: false }" class="bg-blue-700 dark:bg-gray-900 shadow mb-10">

    <div class="flex py-4 max-w-7xl mx-auto px-2 sm:px-4 lg:px-8">
        <button class="lg:hidden text-gray-100 hover:text-gray-300 rounded-md transition duration-150 ease-in-out mr-1" aria-label="Main menu" aria-expanded="false">

            <svg @click="open = true" class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
            </svg>

        </button>

        <a href='#' class="text-sm text-white hover:text-gray-300">Logo</a>

        <div class="flex flex-grow justify-between">

                <div class="flex flex-grow justify-between">

                    <div class="hidden lg:ml-6 lg:flex">
                        <a href="#" class="ml-8 px-1 text-sm text-white hover:text-gray-300">Blog</a>
                        <a href="#" class="ml-8 px-1 text-sm text-white hover:text-gray-300">About Me</a>
                        <a href="#" class="ml-8 px-1 text-sm text-white hover:text-gray-300">Packages</a>
                        <a href="#" class="ml-8 px-1 text-sm text-white hover:text-gray-300">Books</a>
                        <a href="#" class="ml-8 px-1 text-sm text-white hover:text-gray-300">Contact</a>
                    </div>

                </div>

            </div>
    </div>

    <div x-cloak x-show="open" @click.away="open = false">
        <a href="#" class="block pl-3 py-2 font-medium text-white hover:text-gray-700 hover:bg-white">About Me</a>
        <a href="#" class="block pl-3 py-2 font-medium text-white hover:text-gray-700 hover:bg-white">Packages</a>
        <a href="#" class="block pl-3 py-2 font-medium text-white hover:text-gray-700 hover:bg-white">Books</a>
        <a href="#" class="block pl-3 py-2 font-medium text-white hover:text-gray-700 hover:bg-white">Contact</a>
    </div>

</nav>

This now looks like this:

Source, for this demo is available at https://github.com/dcblogdev/tailwind-menu-demo

Copyright © 2006 - 2024 DC Blog - All rights reserved.