Tutorials 5 min read

How to create a sidebar navigation with Tailwind CSS: JavaScript and Alpine.js

Build a sidebar navigation with Tailwind CSS, two ways: vanilla JavaScript with a backdrop and Escape key, or Alpine.js with sliding transitions.

Today we are building a sidebar navigation: a panel that opens over the page with a backdrop behind it. We’ll build it twice, first with vanilla JavaScript, then with Alpine.js, so you can pick whichever fits your stack.

What is a sidebar?

A sidebar is a panel, usually anchored to one side of the screen, that gives users quick access to navigation, menus, or other content without leaving the page they are on. It typically stays hidden until the user opens it, then slides or fades into view over the rest of the layout.

Use cases

  • Navigation: the classic use, moving between sections of a site or app.
  • Dashboards: keep metrics, filters, and shortcuts within reach while the main area shows the data.
  • E-commerce filters: let shoppers narrow products down by category, price, or specs.
  • Documentation sites: a structured table of contents sitting next to the content.

…and many other use cases that I can’t think of right now.

The markup

Classes are removed for brevity, but I’ll keep those classes relevant to the tutorial.

The button to open the sidebar

  • id="openSidebar": the id of the button that opens the sidebar.
html
<button id="openSidebar">
  <!--- SVG or text goes here -->
</button>

The button to close the sidebar

  • id="closeSidebar": the id of the button that closes the sidebar.
html
<button id="closeSidebar">
  <!--- SVG or text goes here -->
</button>

The sidebar’s structure

  • id="sidebar": the id of the sidebar, hidden by default with style="display: none;".
  • id="backdrop": the id of the backdrop, also hidden by default.
  • class="relative z-50": makes the backdrop appear on top of the page.
  • <div class="fixed inset-0 bg-zinc-200/50"></div>: the semi-transparent layer that covers the page behind the sidebar.
html
<div id="sidebar" style="display: none;">
  <!-- Overlay -->
  <div id="backdrop" class="relative z-50" style="display: none;">
    <div class="fixed inset-0 bg-zinc-200/50"></div>
    <!-- Sidebar content -->
    <div>
      <!-- Close button goes here -->
      <div
        class="flex flex-col p-12 pb-2 overflow-y-auto bg-orange-600 grow rounded-3xl"
      >
        <!-- Placeholder content, replace with your own -->
      </div>
    </div>
  </div>
</div>

The script

  • document.addEventListener("DOMContentLoaded", () => {: waits until the page is loaded.
  • toggleSidebar(show): one function that shows or hides both the sidebar and the backdrop by flipping their display values.
  • openButton.addEventListener("click", () => toggleSidebar(true));: the open button shows everything; the close button does the same with false.
  • backdrop.addEventListener("click", () => toggleSidebar(false));: clicking outside the sidebar closes it too.
  • window.addEventListener("keydown", ...): pressing Escape also closes it.
js
document.addEventListener("DOMContentLoaded", () => {
  const sidebar = document.getElementById("sidebar");
  const backdrop = document.getElementById("backdrop");
  const openButton = document.getElementById("openSidebar");
  const closeButton = document.getElementById("closeSidebar");
  const toggleSidebar = (show) => {
    sidebar.style.display = show ? "flex" : "none";
    backdrop.style.display = show ? "block" : "none";
  };
  openButton.addEventListener("click", () => toggleSidebar(true));
  closeButton.addEventListener("click", () => toggleSidebar(false));
  backdrop.addEventListener("click", () => toggleSidebar(false));
  window.addEventListener("keydown", (e) => {
    if (e.key === "Escape") toggleSidebar(false);
  });
});

The Alpine.js version

Same component, no separate script: the state lives in x-data. This version slides the panel in and out with transform classes instead of toggling display.

  • x-data="{ open: false }": initializes the component state; think of open as the on/off switch for the sidebar.
  • @click="open = !open": flips the switch.
  • :class="{'translate-x-0': open, '-translate-x-full': !open}": changes the classes based on the state, so the sidebar slides in and out of view.
html
<div x-data="{ open: false }">
  <button @click="open = !open">Toggle Sidebar</button>
  <div
    class="sidebar"
    :class="{'translate-x-0': open, '-translate-x-full': !open}"
  >
    <!-- Sidebar content goes here -->
  </div>
</div>

Want the sidebar to close on its own when the window is resized past a breakpoint? Add a listener:

  • @resize.window="if (window.innerWidth > 768) open = false": listens for the window’s resize event and hides the sidebar once the width exceeds 768 pixels.
html
<div
  x-data="{ open: false }"
  @resize.window="if (window.innerWidth > 768) open = false"
>
  <button @click="open = !open">Toggle Sidebar</button>
  <div
    class="sidebar"
    :class="{'translate-x-0': open, '-translate-x-full': !open}"
  >
    <!-- Sidebar content here -->
  </div>
</div>

And here is the semi-transparent overlay that closes the sidebar when clicked, the Alpine take on the backdrop from the JavaScript version:

  • x-show="open" with @click="open = false": the overlay only shows while the sidebar is open, and clicking it closes everything.
  • x-transition:enter and x-transition:leave: fade the overlay in and out smoothly.
html
<div x-data="{ open: false }">
  <button @click="open = !open">Toggle Sidebar</button>
  <div
    class="fixed inset-0 z-40 bg-foreground bg-opacity-50"
    x-show="open"
    @click="open = false"
    x-transition:enter="ease-out duration-300"
    x-transition:leave="ease-in duration-200"
    style="display: none;"
  ></div>
  <div
    class="sidebar"
    :class="{'translate-x-0': open, '-translate-x-full': !open}"
  >
    <!-- Sidebar content here -->
  </div>
</div>

You can try the Alpine version separately: live demo and source code.

Which one should you use?

If Alpine is already on the page, the directive version gives you sliding transitions and the overlay with no extra script. If not, the vanilla version covers open, close, backdrop click, and the Escape key without adding a dependency.

Conclusion

A sidebar is just a hidden panel, a backdrop, and a little bit of state. Customize it to fit your design preferences, and remember to make it fully accessible for all users.

Hope you enjoyed this tutorial and have a great day!

/Michael Andreuzza