ZTatman
ZTatman

Reputation: 13

How do I animate the height of a dropdown menu using Tailwind css

I am using: Tailwindcss, React, and Next.js for a side project.

I am trying to create a responsive navbar, that shows a hamburger menu when the screen size reaches tailwinds defined "sm" size.

When I click the hamburger icon, I want the menu to transition from a height of 0 to a max-h-40.

I feel like I am missing something trivial here in my code below, hoping another person looking at this can see what I am missing??

navbar.tsx

"use client";

import Image from "next/image";
import Link from "next/link";
import {
  useState
} from "react";
import logo from "../public/finallang_favicon.ico";

export default function Navbar() {
  const [showMenu, setShowMenu] = useState(false);
  const toggleMenu = () => {
    setShowMenu(!showMenu);
  };
  return ( <
    div >
    <
    nav className = "flex items-center justify-between flex-grow w-auto py-3 text-center border-b px-9 sm:w-auto" >
    <
    div className = "flex items-center justify-center flex-shrink-0 sm:mr-6" >
    <
    Link href = "/" >
    <
    Image src = {
      logo
    }
    alt = "Logo"
    width = {
      48
    }
    height = {
      48
    }
    /> <
    /Link> <
    /div> <
    div className = "hidden text-sm sm:block" >
    <
    Link href = "#"
    className = "block mt-4 sm:mr-4 text-slate-900 hover:text-slate-700 sm:mt-0 sm:inline-block" >
    About <
    /Link> <
    Link href = "#"
    className = "block mt-4 sm:mr-4 text-slate-900 hover:text-slate-700 sm:mt-0 sm:inline-block" >
    Blog <
    /Link> <
    Link href = "#"
    className = "block mt-4 text-slate-900 hover:text-slate-700 sm:mt-0 sm:inline-block" >
    Contact Me <
    /Link> <
    /div> <
    div >
    <
    button className = "hidden px-4 py-2 text-sm leading-none rounded text-slate-100 hover:text-white sm:inline-block bg-brand" >
    Download <
    /button> <
    button onClick = {
      toggleMenu
    }
    aria - label = "Toggle navigation menu"
    className = "text-gray-400 align-middle sm:hidden hover:text-gray-900 focus:ring-2 rounded-md" >
    <
    svg xmlns = "http://www.w3.org/2000/svg"
    fill = "none"
    viewBox = "0 0 24 24"
    strokeWidth = {
      2
    }
    stroke = "currentColor"
    className = "w-6 h-6" >
    <
    path strokeLinecap = "round"
    strokeLinejoin = "round"
    d = "M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" / >
    <
    /svg> <
    /button> <
    /div> <
    /nav> {
      showMenu &&
        <
        div className = {
          `${showMenu ? "max-h-40" : "h-0"} text-sm text-center sm:hidden transition-all duration-500 ease-in-out overflow-hidden`
        } >
        <
        Link href = "/about"
      className = "block mt-4 text-slate-900 hover:text-slate-700" >
        About <
        /Link> <
        Link href = "/blog"
      className = "block mt-4 text-slate-900 hover:text-slate-700" >
        Blog <
        /Link> <
        Link href = "/contact"
      className = "block mt-4 text-slate-900 hover:text-slate-700" >
        Contact Me <
        /Link> <
        /div>
    } <
    /div>
  );
}

Things I have tried:

Current Behavior: Gif of current behavior

What I expect to happen:

Upvotes: 1

Views: 4787

Answers (1)

Wongjn
Wongjn

Reputation: 24303

The problem explained

DOM mounting

The conditional rendering via the snippet:

{showMenu &&
  <div className="…">

Means that the element gets mounted into the DOM or out of the DOM. Transitions do not play on the same frame that the element is mounted/unmounted.

CSS property transitions

Furthermore, you are changing different CSS properties with the conditional classes for the menu container:

${showMenu ? "max-h-40" : "h-0"}

max-h-40 corresponds to max-height: 10rem while h-0 corresponds to height: 0. This means that we are changing two values here from their initial values, max-height and height. According to MDN, the initial value for max-height is none and the initial value for height is auto. The values change with respect to showMenu as follows:

showMenu true false
max-height 10rem none
height auto 0

However, no transition can happen between height: 0height: auto, and max-height: nonemax-height: 10rem would create a transition from full height to 10rem height which does not seem what you'd like either!

Solution

  • We would need to keep the menu container mounted to the DOM at all times. No conditional mounting.
  • Transition between max-height: 0max-height: 10rem
  • Use visibility: hidden to hide the element so that one cannot tab-focus into hidden children.
return (
  <div>
    …
    <div className={`${showMenu ? "max-h-40" : "max-h-0 invisible"} text-sm text-center sm:hidden transition-all duration-500 ease-in-out overflow-hidden`}>
      …
    </div>
  </div>
);

Upvotes: 2

Related Questions