Holidays Deal Full Access for 50% OFF. Use code LEX50 at checkout.

You'll get every theme available plus future additions. That's 45 themes total. Unlimited projects. Lifetime updates. One payment.

How to build a full-screen navigation with Tailwind CSS and JavaScript

Learn how to create a smooth, full-screen overlay navigation menu using Tailwind CSS for styling and vanilla JavaScript for state management.

Published on December 6, 2025 by Michael Andreuzza

Full-screen navigation menus are excellent for creating immersive experiences and handling complex site structures without cluttering the header.

In this tutorial, we’ll build a responsive full-screen menu that slides down from the top, locking the body scroll to prevent background movement.


Why Use a Full-Screen Navigation?

Full-screen navigations are popular in modern web design for several reasons:

  • Visual Impact: They create a bold, immersive experience that draws attention to your navigation items.
  • Mobile-First Thinking: The pattern works seamlessly across all screen sizes without needing separate mobile/desktop implementations.
  • Content Flexibility: You have the entire viewport to display navigation links, images, contact information, or even promotional content.
  • Focus: When open, users can focus entirely on navigation without distractions from the page content.

This pattern is commonly used by agencies, portfolios, and brands that want to make a strong visual statement.


The Structure

We use a fixed header for the toggle button and a fixed overlay div for the menu itself. The overlay is hidden by default using a negative translate transform.

Breaking Down the HTML

The structure consists of two main parts:

  1. The Navigation Bar: A fixed header containing the logo, optional contact info, and the menu toggle button.
  2. The Full-Screen Overlay: A fixed div that covers the entire viewport when opened.

Key Tailwind Classes for the Overlay

fixed inset-0 bg-white z-[60] transition-transform duration-500 ease-in-out flex flex-col overflow-hidden -translate-y-full

Let’s break these down:

  • fixed inset-0: Positions the overlay to cover the entire viewport.
  • z-[60]: Ensures the overlay sits above the navigation bar (which uses z-50).
  • transition-transform duration-500 ease-in-out: Creates a smooth 500ms animation when the menu opens/closes.
  • -translate-y-full: Moves the overlay completely off-screen (above the viewport) by default.
  • overflow-hidden: Prevents any content from spilling outside the overlay.

When we remove -translate-y-full, the overlay animates smoothly into view thanks to the transition-transform property.


The Logic

We need a small script to handle the toggling. The key here is handling the overflow-hidden on the body to prevent scrolling when the menu is open, and calculating the scrollbar width to prevent layout shifts.

The Toggle Function Explained

function toggleMenu() {
  if (!fullscreenMenu) return;

  if (fullscreenMenu.classList.contains("-translate-y-full")) {
    // Open Menu
    const scrollbarWidth =
      window.innerWidth - document.documentElement.clientWidth;
    document.body.style.paddingRight = `${scrollbarWidth}px`;
    document.body.classList.add("overflow-hidden");
    fullscreenMenu.classList.remove("-translate-y-full");
  } else {
    // Close Menu
    document.body.classList.remove("overflow-hidden");
    document.body.style.paddingRight = "";
    fullscreenMenu.classList.add("-translate-y-full");
  }
}

What’s Happening Here?

  1. State Check: We check if the menu is currently hidden by looking for the -translate-y-full class.
  2. Scrollbar Width Calculation: Before opening, we calculate the scrollbar width to prevent layout shift.
  3. Body Padding Compensation: We add padding equal to the scrollbar width so the content doesn’t jump.
  4. Scroll Lock: We add overflow-hidden to the body to prevent background scrolling.
  5. Toggle Animation: We remove/add the translate class to trigger the CSS transition.

Key Implementation Details

1. Transition State

We use -translate-y-full to hide the menu off-screen (top). Removing this class lets the transition-transform property animate it into view (translate-y-0).

This approach is more performant than animating top or height properties because transforms are GPU-accelerated and don’t trigger layout recalculations.

2. Scroll Locking

When the menu opens, we add overflow-hidden to the body. This prevents users from scrolling the background content while the menu is open.

Without this, users could scroll the page behind the overlay, which creates a confusing experience.

3. Layout Shift Prevention

This is the most commonly overlooked detail. When we hide the scrollbar with overflow-hidden, the page content shifts slightly to the right (by the width of the scrollbar).

We solve this by:

  1. Calculating the scrollbar width: window.innerWidth - document.documentElement.clientWidth
  2. Adding that value as padding-right to the body before hiding the scrollbar

This ensures the content stays perfectly still when the menu opens and closes.


Accessibility Considerations

When building full-screen navigations, keep these accessibility best practices in mind:

Keyboard Navigation

  • Ensure all menu links are focusable and can be navigated with the Tab key.
  • Add keyboard support to close the menu with the Escape key.
document.addEventListener("keydown", (e) => {
  if (
    e.key === "Escape" &&
    !fullscreenMenu.classList.contains("-translate-y-full")
  ) {
    toggleMenu();
  }
});

ARIA Attributes

Consider adding ARIA attributes for better screen reader support:

<button
  id="menu-toggle"
  aria-expanded="false"
  aria-controls="fullscreen-menu"
  aria-label="Toggle Menu"
></button>

Update aria-expanded in your JavaScript when toggling:

menuToggle.setAttribute("aria-expanded", isOpen ? "true" : "false");

Focus Management

When the menu opens, consider moving focus to the first menu item or the close button. When it closes, return focus to the toggle button.


Customization Ideas

Different Animation Directions

Instead of sliding from the top, you can slide from other directions:

  • From bottom: Use translate-y-full instead of -translate-y-full
  • From left: Use -translate-x-full
  • From right: Use translate-x-full

Fade Effect

Add opacity to create a fade-in effect:

opacity-0 transition-all duration-500

Then toggle opacity-100 along with the transform.

For a more polished feel, animate each link with a slight delay:

.menu-link {
  opacity: 0;
  transform: translateY(20px);
  transition: all 0.3s ease;
}

.menu-open .menu-link {
  opacity: 1;
  transform: translateY(0);
}

.menu-link:nth-child(1) {
  transition-delay: 0.1s;
}
.menu-link:nth-child(2) {
  transition-delay: 0.15s;
}
.menu-link:nth-child(3) {
  transition-delay: 0.2s;
}
/* ... and so on */

The Full Code

Here is the complete code separated into HTML structure and JavaScript logic.

HTML Structure

<nav class="fixed w-full top-0 z-50 bg-white">
  <div class="max-w-screen mx-auto px-8 w-full 2xl:max-w-[100rem]">
    <div class="flex py-2 items-start lg:items-center justify-between">
      <!-- Logo -->
      <a href="/" aria-label="Home" class="focus:outline-none">
        <svg
          class="h-8 text-base-900"
          viewBox="0 0 80 80"
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            fill-rule="evenodd"
            clip-rule="evenodd"
            d="M0 0V80H80V0H0ZM8.68501 35.7187H71.315V8.68502H8.68501V35.7187ZM71.315 71.315H8.68501V44.4037H71.315V71.315Z"
            fill="currentColor"
          ></path>
        </svg>
      </a>

      <!-- Contact Info (Optional) -->
      <div class="flex flex-col text-center">
        <a
          href="mailto:hello@rosewood.com"
          class="text-[0.65rem] text-base-600 font-medium uppercase tracking-wide hover:text-bse-500 transition-colors"
        >
          hello@rosewood.com
        </a>
        <a
          href="tel:+15551234567"
          class="text-[0.65rem] text-base-600 font-medium uppercase tracking-wide hover:text-bse-500 transition-colors"
        >
          +1 (555) 123-4567
        </a>
      </div>

      <!-- Menu Toggle Button -->
      <button
        id="menu-toggle"
        class="focus:outline-none"
        aria-label="Toggle Menu"
      >
        <span class="sr-only">Menu</span>
        <svg
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 256 256"
          fill="currentColor"
          class="icon size-6 group-hover:text-bse-500 transition-colors"
        >
          <path
            d="M228,160a12,12,0,0,1-12,12H40a12,12,0,0,1,0-24H216A12,12,0,0,1,228,160ZM40,108H216a12,12,0,0,0,0-24H40a12,12,0,0,0,0,24Z"
          ></path>
        </svg>
      </button>
    </div>
  </div>

  <!-- Full Screen Overlay -->
  <div
    id="fullscreen-menu"
    class="fixed inset-0 bg-white z-[60] transition-transform duration-500 ease-in-out flex flex-col overflow-hidden -translate-y-full"
  >
    <div
      class="max-w-screen mx-auto px-8 w-full 2xl:max-w-[100rem] py-2 h-full flex flex-col"
    >
      <!-- Header inside overlay (Logo + Close Button) -->
      <div class="flex items-center justify-between">
        <a href="/" aria-label="Home" class="focus:outline-none">
          <svg
            class="h-8 text-base-900"
            viewBox="0 0 80 80"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              fill-rule="evenodd"
              clip-rule="evenodd"
              d="M0 0V80H80V0H0ZM8.68501 35.7187H71.315V8.68502H8.68501V35.7187ZM71.315 71.315H8.68501V44.4037H71.315V71.315Z"
              fill="currentColor"
            ></path>
          </svg>
        </a>
        <button id="menu-close" class="focus:outline-none">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 256 256"
            fill="currentColor"
            class="size-6 text-base-900 hover:text-bse-500 transition-colors"
          >
            <path
              d="M208.49,191.51a12,12,0,0,1-17,17L128,145,64.49,208.49a12,12,0,0,1-17-17L111,128,47.51,64.49a12,12,0,0,1,17-17L128,111l63.51-63.52a12,12,0,0,1,17,17L145,128Z"
            ></path>
          </svg>
        </button>
      </div>

      <!-- Menu Content -->
      <div class="grid grid-cols-1 lg:grid-cols-3 items-start h-full mt-2">
        <h1
          class="text-5xl sm:text-5xl md:text-6xl lg:text-7xl text-base-900 leading-none font-display"
        >
          Menu
        </h1>
        <div class="flex flex-col gap-2 mt-auto lg:mt-0">
          <a
            href="/system/overview"
            class="w-fit text-base-900 text-base block hover:text-base-500 duration-300"
            >Overview</a
          >
          <a
            href="https://buy.polar.sh/polar_cl_R1DzAX5Wlvr5Qnqx8VysnG7tkliXHnU4qs9bQ0AaQMb"
            class="w-fit text-base-900 text-base block hover:text-base-500 duration-300"
            >Buy Rosewood</a
          >
          <a
            href="/services"
            class="w-fit text-base-900 text-base block hover:text-base-500 duration-300"
            >Services</a
          >
          <a
            href="/projects"
            class="w-fit text-base-900 text-base block hover:text-base-500 duration-300"
            >Projects</a
          >
          <a
            href="/about"
            class="w-fit text-base-900 text-base block hover:text-base-500 duration-300"
            >About</a
          >
          <a
            href="/process"
            class="w-fit text-base-900 text-base block hover:text-base-500 duration-300"
            >Process</a
          >
          <a
            href="/blog"
            class="w-fit text-base-900 text-base block hover:text-base-500 duration-300"
            >Blog</a
          >
          <a
            href="/contact"
            class="w-fit text-base-900 text-base block hover:text-base-500 duration-300"
            >Contact</a
          >
        </div>
        <img
          src="/_astro/hero.DQ0YEZB6_ZIJDlc.webp"
          alt="Rosewood Home Improvement"
          loading="lazy"
          decoding="async"
          fetchpriority="auto"
          width="1200"
          height="1200"
          class="size-full object-cover hidden lg:block"
        />
      </div>
    </div>
  </div>
</nav>

JavaScript

const menuToggle = document.getElementById("menu-toggle");
const menuClose = document.getElementById("menu-close");
const fullscreenMenu = document.getElementById("fullscreen-menu");

function toggleMenu() {
  if (!fullscreenMenu) return;

  if (fullscreenMenu.classList.contains("-translate-y-full")) {
    // Open Menu
    const scrollbarWidth =
      window.innerWidth - document.documentElement.clientWidth;
    document.body.style.paddingRight = `${scrollbarWidth}px`;
    document.body.classList.add("overflow-hidden");
    fullscreenMenu.classList.remove("-translate-y-full");
  } else {
    // Close Menu
    document.body.classList.remove("overflow-hidden");
    document.body.style.paddingRight = "";
    fullscreenMenu.classList.add("-translate-y-full");
  }
}

menuToggle?.addEventListener("click", toggleMenu);
menuClose?.addEventListener("click", toggleMenu);

// Close on Escape key
document.addEventListener("keydown", (e) => {
  if (
    e.key === "Escape" &&
    !fullscreenMenu?.classList.contains("-translate-y-full")
  ) {
    toggleMenu();
  }
});

Conclusion

This approach ensures a smooth, professional feel that works across devices. The key takeaways are:

  1. Use CSS transforms for performant animations.
  2. Always handle scroll locking to prevent background scrolling.
  3. Compensate for scrollbar width to avoid layout shifts.
  4. Consider accessibility with keyboard navigation and ARIA attributes.

With these foundations in place, you can customize the design to match your brand while maintaining a solid, accessible user experience.

/Michael Andreuzza