Mobile Navigation Drawer

Introduction: Why Mobile Navigation Matters

Welcome back!
In the last few lessons, you focused on features like task filtering, cross-view synchronization, and user feedback — all essential for a seamless desktop experience.

Now it’s time to make your app truly responsive.
Modern web users expect applications to adapt beautifully whether they’re on a laptop, tablet, or smartphone.

On larger screens, side navigation works perfectly — you have plenty of horizontal space.
But on smaller screens, that same sidebar eats up valuable screen real estate.

The solution? A responsive navigation system:

  • A fixed sidebar on desktop.
  • A hamburger-style drawer menu on mobile.

By the end of this lesson, you’ll understand exactly how this is built — how to structure it, control it, and style it so your app feels just as smooth on mobile as it does on desktop.

Big Picture: How the Layout Responds to Screen Size

Your DashboardLayout component is now fully responsive:

  • On large screens (md and up) → you’ll see a permanent sidebar on the left.
  • On small screens (below md) → the sidebar is hidden, replaced by:
    • A hamburger menu button in the top header.
    • A sliding drawer menu that appears when the button is clicked.
    • A dimmed overlay that closes the menu when tapped.

This behavior is implemented with a combination of:

  • Tailwind’s responsive utilities (hidden md:block, md:hidden, etc.)
  • React state to open/close the mobile drawer.
  • ARIA attributes and keyboard-friendly icons for accessibility.

Let’s explore how each part works.

Desktop Sidebar: The Base Navigation Structure

Here’s the sidebar portion from src/app/(dashboard)/layout.tsx:

<aside className="hidden md:block border-r bg-white">
  <div className="p-4 text-xl font-semibold">TaskManager</div>
  <nav className="px-2 space-y-1">
    {navItems.map((item) => (
      <Link
        key={item.href}
        href={item.href}
        className={`block rounded-md px-3 py-2 text-sm font-medium hover:bg-gray-100 ${
          isActive(item.href) ? 'bg-gray-100 text-gray-900' : 'text-gray-700'
        }`}
      >
        {item.label}
      </Link>
    ))}
  </nav>
</aside>

Key details:

  • hidden md:block → the sidebar is hidden on mobile but visible from md breakpoint (768px) and up.
  • border-r bg-white → gives the sidebar a solid white background with a subtle right border to separate it from the main area.
  • space-y-1 → adds consistent spacing between navigation links.
  • isActive(item.href) → determines which route is currently active to highlight the link (important for user orientation).

Each <Link> uses Tailwind classes for hover effects, padding, and rounded corners.

This structure works well on desktop — always visible, stable, and non-intrusive.
Now let’s focus on the mobile experience.

Mobile Header: The Hamburger Menu

When you switch to mobile view, the sidebar disappears — and a header bar replaces it:

<header className="sticky top-0 z-10 border-b bg-white/80 backdrop-blur h-14 flex items-center justify-between px-4">
  <button
    onClick={() => setMobileOpen(true)}
    className="md:hidden p-2 rounded-md hover:bg-gray-100"
    aria-label="Open menu"
  >
    <svg className="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor">
      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
    </svg>
  </button>
  <div className="font-semibold">TaskManager</div>
  <div className="w-10 md:hidden" />
</header>

What’s happening:

  • The header stays sticky at the top and uses a blurred, semi-transparent background (bg-white/80 backdrop-blur) to look polished while scrolling.
  • The hamburger menu button (md:hidden) only appears on small screens.

It’s styled with:

  • p-2 padding for a good touch target.
  • rounded-md hover:bg-gray-100 for smooth hover/tap feedback.

The button triggers setMobileOpen(true) — setting React state that opens the mobile drawer.

The small empty <div className="w-10 md:hidden" /> on the right keeps spacing symmetrical when the hamburger icon is visible on the left.

Mobile Drawer: The Sliding Navigation Menu

When mobileOpen is true, this section renders:

{mobileOpen && (
  <div className="md:hidden">
    <div className="fixed inset-0 bg-black/50 z-40" onClick={closeMobile} />
    <aside className="fixed inset-y-0 left-0 w-64 bg-white shadow-xl z-50">
      <div className="flex items-center justify-between p-4 border-b">
        <div className="text-xl font-semibold">TaskManager</div>
        <button
          onClick={closeMobile}
          className="p-2 rounded-md hover:bg-gray-100"
          aria-label="Close menu"
        >
          <svg className="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
          </svg>
        </button>
      </div>
      <nav className="px-2 py-4 space-y-1">
        {navItems.map((item) => (
          <Link
            key={item.href}
            href={item.href}
            onClick={closeMobile}
            className={`block rounded-md px-3 py-2 text-sm font-medium hover:bg-gray-100 ${
              isActive(item.href) ? 'bg-gray-100 text-gray-900' : 'text-gray-700'
            }`}
          >
            {item.label}
          </Link>
        ))}
      </nav>
    </aside>
  </div>
)}

Let’s unpack this step by step:

1. mobileOpen State Controlled with React’s useState(false) hook.
Toggled via:

  • Hamburger buttonsetMobileOpen(true)
  • Overlay click or Close buttonsetMobileOpen(false)

This state determines whether the drawer is visible.

2. The Overlay

<div className="fixed inset-0 bg-black/50 z-40" onClick={closeMobile} />
  • Covers the entire screen with a dark semi-transparent layer.
  • Prevents interaction with the main content.
  • Clicking it closes the drawer — intuitive for mobile users.

Tailwind details:

  • fixed inset-0 → covers the entire viewport.
  • bg-black/50 → creates a 50% opacity dark overlay.
  • z-40 → ensures it appears beneath the drawer (z-50).

3. The Drawer Itself

<aside className="fixed inset-y-0 left-0 w-64 bg-white shadow-xl z-50">
  • fixed inset-y-0 left-0 → pins the drawer to the left edge, full height.
  • w-64 → gives it a consistent 256px width.
  • bg-white shadow-xl → matches desktop styling but adds a soft elevation for the sliding effect.
  • z-50 → ensures it appears above the overlay.

4. Drawer Header

<div className="flex items-center justify-between p-4 border-b">
  • Displays the app name and a close icon.
  • Close button uses:
    • A minimalist X SVG icon (<path d="M6 18L18 6M6 6l12 12" />)
    • aria-label="Close menu" for accessibility.
    • hover:bg-gray-100 to match the rest of the UI’s hover pattern.

5. Navigation Links Same logic as the desktop sidebar:

  • Mapped from the same navItems array.
  • Styled identically for visual consistency.
  • Clicking a link also calls closeMobile() to collapse the drawer after navigation.

6. Z-index and Layering

  • Drawer (z-50) sits above the overlay (z-40).
  • Overlay ensures focus remains on the open menu.
  • The rest of the page content remains visible but inactive underneath.

Tailwind callout: The responsive behavior comes mainly from hidden md:block for the desktop sidebar, md:hidden for mobile-only controls, fixed inset-0 for the overlay, and fixed inset-y-0 left-0 w-64 for the drawer panel.

Accessibility & UX Considerations

This layout also follows best accessibility practices:

  • aria-label attributes → clarify the purpose of buttons for screen readers.
  • Keyboard navigability → users can tab through links easily since they’re semantic <a> tags via Link.
  • Visual consistency → colors, hover feedback, and spacing align with the desktop layout.

It’s not just mobile-friendly — it’s inclusive and usable for everyone.

Putting It All Together

Here’s how your layout behaves across devices:

Device TypeSidebar BehaviorNavigation Trigger
Desktop (md+)Permanent sidebar visibleNone (always open)
Tablet / Mobile (<md)Sidebar hiddenMenu button (hamburger icon)
Menu open on mobileDrawer slides in from leftOverlay click or Close button hides it

No extra routes or components — just one smart layout component that adapts seamlessly to screen size.

Summary

In this lesson, you learned how to:

  • Add a mobile navigation drawer that slides in and out with React state.
  • Use Tailwind’s responsive classes to toggle between sidebar and drawer layouts.
  • Combine overlay, drawer, and header layers for an intuitive experience.
  • Maintain accessibility with ARIA labels and consistent focus patterns.
  • Build a single unified layout that works perfectly on all screen sizes.

Your Task Manager app is now fully responsive — with a professional, production-grade navigation system that adapts to any device.

This wraps up the course: you’ve added URL-driven filtering, synchronized state across views, and improved the layout for mobile users.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal