DEV Community

Cover image for Creating a Mobile Responsive Menu in Tailwind CSS
UI Junkie
UI Junkie

Posted on

Creating a Mobile Responsive Menu in Tailwind CSS

Every great portfolio needs a navigation menu that works seamlessly across all devices. In this tutorial, you'll learn how to transform a standard desktop navigation into a sleek, full-screen mobile menu using Tailwind CSS—inspired by the WebDev portfolio template from TemplatesJungle.

We'll cover the essential HTML structure, Tailwind utility classes for responsive behavior, smooth slide-in animations, and the JavaScript to tie it all together. By the end, you'll have a professional, mobile-friendly menu that keeps your portfolio looking sharp on every screen size. Let's dive in.

Overview

The template implements a responsive navigation pattern that:

  • Shows a horizontal navigation bar on desktop (md breakpoint and above)
  • Displays a hamburger menu button on mobile
  • Opens a full-screen overlay menu when the hamburger is clicked
  • Uses smooth transitions and animations

HTML Structure

Here's the key HTML structure from the template:

Desktop Navigation

<header class="fixed top-0 w-full z-50 bg-surface/60 backdrop-blur-[24px] border-b border-on-surface/10">
    <div class="flex justify-between items-center h-20 px-margin-mobile md:px-margin-desktop max-w-container-max mx-auto">
        <!-- Logo -->
        <a class="font-headline-md text-headline-md font-black text-primary tracking-tighter hover:opacity-80 transition-opacity" 
           href="#">WEBDEV</a>

        <!-- Desktop Navigation - Hidden on mobile, visible on md+ -->
        <nav class="hidden md:flex items-center gap-8">
            <a class="text-primary border-b-2 border-primary pb-1 font-label-caps text-label-caps transition-all" 
               href="#">Projects</a>
            <a class="text-on-surface-variant font-label-caps text-label-caps hover:text-primary transition-colors hover:translate-y-[-1px]" 
               href="#">Experience</a>
            <a class="text-on-surface-variant font-label-caps text-label-caps hover:text-primary transition-colors hover:translate-y-[-1px]" 
               href="#">About</a>
            <a class="text-on-surface-variant font-label-caps text-label-caps hover:text-primary transition-colors hover:translate-y-[-1px]" 
               href="#">Contact</a>
        </nav>

        <!-- Mobile Menu Button - Visible only on mobile -->
        <button id="mobile-menu-open" 
                class="md:hidden flex items-center justify-center p-2 text-on-surface hover:text-primary transition-colors">
            <span class="material-symbols-outlined text-3xl">menu</span>
        </button>

        <!-- Resume Button - Hidden on mobile -->
        <button class="hidden md:flex shimmer-btn text-on-primary px-6 py-2 rounded-full font-label-caps text-label-caps hover:drop-shadow-[0_0_15px_rgba(219,252,255,0.4)] active:scale-95 transition-all duration-200">
            Resume
        </button>
    </div>
</header>
Enter fullscreen mode Exit fullscreen mode

Mobile Overlay Menu

<div id="mobile-menu-overlay" 
     class="fixed inset-0 z-[60] bg-background/95 backdrop-blur-xl translate-x-full transition-transform duration-500 md:hidden flex flex-col">

    <!-- Overlay Header -->
    <div class="flex justify-between items-center h-20 px-margin-mobile">
        <a class="font-headline-md text-headline-md font-black text-primary tracking-tighter" href="#">WEBDEV</a>
        <button id="mobile-menu-close" class="p-2 text-on-surface hover:text-primary transition-colors">
            <span class="material-symbols-outlined text-3xl">close</span>
        </button>
    </div>

    <!-- Mobile Navigation Links -->
    <nav class="flex flex-col items-center justify-center flex-grow gap-10">
        <a class="text-primary text-2xl font-headline-md" href="#">Projects</a>
        <a class="text-on-surface-variant text-2xl font-headline-md hover:text-primary transition-colors" href="#">Experience</a>
        <a class="text-on-surface-variant text-2xl font-headline-md hover:text-primary transition-colors" href="#">About</a>
        <a class="text-on-surface-variant text-2xl font-headline-md hover:text-primary transition-colors" href="#">Contact</a>
        <button class="shimmer-btn text-on-primary px-10 py-3 rounded-full font-label-caps text-label-caps mt-4">
            Resume
        </button>
    </nav>
</div>
Enter fullscreen mode Exit fullscreen mode

CSS Classes Explained

Key Tailwind Utility Classes Used:

Class Purpose
hidden md:flex Hide on mobile, show as flex on medium screens and above
md:hidden Show on mobile, hide on medium screens
fixed inset-0 Position overlay to cover entire viewport
z-50 / z-[60] Stacking context (overlay appears above header)
bg-background/95 backdrop-blur-xl Semi-transparent background with blur effect
translate-x-full Initially position overlay off-screen to the right
transition-transform duration-500 Smooth animation when toggling
flex flex-col Vertical flex layout for overlay content

JavaScript Interactivity

Here's the JavaScript needed to make the menu functional:

// script.js
document.addEventListener('DOMContentLoaded', function() {
    const mobileMenuButton = document.getElementById('mobile-menu-open');
    const mobileMenuClose = document.getElementById('mobile-menu-close');
    const mobileMenuOverlay = document.getElementById('mobile-menu-overlay');

    // Function to open mobile menu
    function openMobileMenu() {
        mobileMenuOverlay.classList.remove('translate-x-full');
        document.body.style.overflow = 'hidden'; // Prevent background scrolling
    }

    // Function to close mobile menu
    function closeMobileMenu() {
        mobileMenuOverlay.classList.add('translate-x-full');
        document.body.style.overflow = ''; // Restore scrolling
    }

    // Event listeners
    mobileMenuButton.addEventListener('click', openMobileMenu);
    mobileMenuClose.addEventListener('click', closeMobileMenu);

    // Close menu when clicking on a navigation link
    const mobileNavLinks = mobileMenuOverlay.querySelectorAll('nav a');
    mobileNavLinks.forEach(link => {
        link.addEventListener('click', closeMobileMenu);
    });

    // Close menu when pressing Escape key
    document.addEventListener('keydown', function(event) {
        if (event.key === 'Escape' && !mobileMenuOverlay.classList.contains('translate-x-full')) {
            closeMobileMenu();
        }
    });
});
Enter fullscreen mode Exit fullscreen mode

Step-by-Step Implementation Guide

Step 1: Set Up Your HTML Structure

Start with a fixed header containing your logo, desktop navigation, and mobile menu button:

<header class="fixed top-0 w-full z-50 bg-surface/60 backdrop-blur-[24px] border-b border-on-surface/10">
    <div class="flex justify-between items-center h-20 px-4 md:px-8 max-w-7xl mx-auto">
        <!-- Logo -->
        <a href="/" class="text-xl font-bold text-primary">LOGO</a>

        <!-- Desktop Navigation -->
        <nav class="hidden md:flex items-center gap-6">
            <a href="#" class="hover:text-primary transition">Home</a>
            <a href="#" class="hover:text-primary transition">About</a>
            <a href="#" class="hover:text-primary transition">Services</a>
            <a href="#" class="hover:text-primary transition">Contact</a>
        </nav>

        <!-- Mobile Menu Button -->
        <button id="menu-btn" class="md:hidden p-2">
            <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
            </svg>
        </button>
    </div>
</header>
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the Mobile Overlay

Place this after your header but before closing the body tag:

<div id="mobile-menu" class="fixed inset-0 z-50 bg-black/95 backdrop-blur-lg transform translate-x-full transition-transform duration-300 md:hidden">
    <div class="flex justify-between items-center h-20 px-4 border-b border-white/10">
        <a href="/" class="text-xl font-bold text-primary">LOGO</a>
        <button id="close-menu" class="p-2">
            <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
            </svg>
        </button>
    </div>

    <nav class="flex flex-col items-center justify-center h-full gap-8">
        <a href="#" class="text-2xl hover:text-primary transition">Home</a>
        <a href="#" class="text-2xl hover:text-primary transition">About</a>
        <a href="#" class="text-2xl hover:text-primary transition">Services</a>
        <a href="#" class="text-2xl hover:text-primary transition">Contact</a>
    </nav>
</div>
Enter fullscreen mode Exit fullscreen mode

Step 3: Add JavaScript Toggle Logic

const menuBtn = document.getElementById('menu-btn');
const closeBtn = document.getElementById('close-menu');
const mobileMenu = document.getElementById('mobile-menu');

function openMenu() {
    mobileMenu.classList.remove('translate-x-full');
    document.body.style.overflow = 'hidden';
}

function closeMenu() {
    mobileMenu.classList.add('translate-x-full');
    document.body.style.overflow = '';
}

menuBtn?.addEventListener('click', openMenu);
closeBtn?.addEventListener('click', closeMenu);

// Close menu on link click
mobileMenu?.querySelectorAll('a').forEach(link => {
    link.addEventListener('click', closeMenu);
});
Enter fullscreen mode Exit fullscreen mode

Customization Options

Change Animation Direction

<!-- Slide from left instead of right -->
<div class="... -translate-x-full transition-transform ...">
Enter fullscreen mode Exit fullscreen mode

Change Overlay Style

<!-- Different background styles -->
<div class="... bg-surface/98 backdrop-blur-sm ...">
<div class="... bg-gradient-to-b from-background to-surface ...">
Enter fullscreen mode Exit fullscreen mode

Add Active Link Highlighting

// Add active class to current page link
const currentPath = window.location.pathname;
mobileMenu.querySelectorAll('a').forEach(link => {
    if (link.getAttribute('href') === currentPath) {
        link.classList.add('text-primary', 'border-b-2', 'border-primary');
    }
});
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Always set position: fixed on the overlay to cover the entire viewport
  2. Disable body scroll when menu is open for better UX
  3. Use high z-index values (z-50 or higher) for overlays
  4. Include both open and close buttons for accessibility
  5. Test on actual mobile devices to verify touch targets (minimum 44x44px)
  6. Add focus management for keyboard navigation
  7. Consider using prefers-reduced-motion for users who prefer less animation

Complete Working Example

Here's a minimal complete example combining everything:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.tailwindcss.com"></script>
    <title>Responsive Menu Demo</title>
</head>
<body class="bg-gray-900 text-white">
    <header class="fixed top-0 w-full z-50 bg-gray-900/80 backdrop-blur-md border-b border-white/10">
        <div class="flex justify-between items-center h-16 px-4 md:px-8 max-w-6xl mx-auto">
            <a href="/" class="text-xl font-bold text-cyan-400">LOGO</a>
            <nav class="hidden md:flex gap-6">
                <a href="#" class="hover:text-cyan-400 transition">Home</a>
                <a href="#" class="hover:text-cyan-400 transition">About</a>
                <a href="#" class="hover:text-cyan-400 transition">Services</a>
                <a href="#" class="hover:text-cyan-400 transition">Contact</a>
            </nav>
            <button id="menuBtn" class="md:hidden p-2">
                <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
                </svg>
            </button>
        </div>
    </header>

    <div id="mobileMenu" class="fixed inset-0 z-50 bg-gray-900/95 backdrop-blur-lg transform translate-x-full transition-transform duration-300 md:hidden">
        <div class="flex justify-between items-center h-16 px-4 border-b border-white/10">
            <a href="/" class="text-xl font-bold text-cyan-400">LOGO</a>
            <button id="closeBtn" class="p-2">
                <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
                </svg>
            </button>
        </div>
        <nav class="flex flex-col items-center justify-center h-full gap-8">
            <a href="#" class="text-2xl hover:text-cyan-400 transition">Home</a>
            <a href="#" class="text-2xl hover:text-cyan-400 transition">About</a>
            <a href="#" class="text-2xl hover:text-cyan-400 transition">Services</a>
            <a href="#" class="text-2xl hover:text-cyan-400 transition">Contact</a>
        </nav>
    </div>

    <main class="pt-20">
        <div class="max-w-6xl mx-auto px-4 py-12">
            <h1 class="text-4xl font-bold mb-4">Responsive Menu Demo</h1>
            <p>Resize your browser to see the mobile menu in action!</p>
        </div>
    </main>

    <script>
        const menuBtn = document.getElementById('menuBtn');
        const closeBtn = document.getElementById('closeBtn');
        const mobileMenu = document.getElementById('mobileMenu');

        menuBtn?.addEventListener('click', () => {
            mobileMenu.classList.remove('translate-x-full');
            document.body.style.overflow = 'hidden';
        });

        closeBtn?.addEventListener('click', () => {
            mobileMenu.classList.add('translate-x-full');
            document.body.style.overflow = '';
        });

        mobileMenu?.querySelectorAll('a').forEach(link => {
            link.addEventListener('click', () => {
                mobileMenu.classList.add('translate-x-full');
                document.body.style.overflow = '';
            });
        });
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

If you're ready to stop wrestling with complex CSS and start building a portfolio that truly reflects your skills, download the free WebDev Tailwind CSS Portfolio Template today from TemplatesJungle.

Whether you need a personal landing page, a freelance portfolio, or a launch pad for your next product, WebDev turns your skills into a stunning, highly customizable website in no time. Grab it for free—for both personal and commercial use—and start impressing clients today.

Top comments (0)