Drop-down animation with Tailwind

Smooth drop-down animation without JS libraries

In order to achieve a smooth dropdown animation without a dedicated JavaScript library, we have to address a common CSS limitation: the height property cannot transition between a fixed value like 0 and auto.
CSS cannot transition between:

 height: 0;
 height: auto;

This limitation often leads to "snapping" menus or causes developers to reach out for Framer Motion or GSAP just to open a simple dropdown.

1. A Solution with HTML

This implementation uses three specific techniques to achieve a smooth, weighted feel:

I. Custom Timing function
Instead of a generic ease, we use a custom Bézier curve: ease-in-out (cubic-bezier(0.4,0,0.2,1). This mimics real-world weight-starting fast, and settling with a smooth deceleration

 <div
        id="dropdown-menu"
        class="grid grid-rows-[0fr] opacity-0 transition-all duration-500 ease-in-out overflow-hidden
               bg-white border-b shadow-xl">
    ...
    <!-- Dropdown menu content -->

</div>

II. CSS Grid for dynamic height
We animate grid-template-rows instead of the height property.
In CSS Grid, animating from 0fr to 1fr only works, as long as the direct child of the grid item has min-height:0 (see below) and the container users overflow:hidden.

  • Closed: grid-rows[0fr]
  • Open: grid-rows[1fr]

The browser calculates the height of the internal content dynamically, and transitions between states correctly.

<div class="min-h-0">
    ...
    <!-- Navigation menu -->
</div>

III. The Staggered effect
To create a cascading reveal effect, the links cannot be static. They must slide up into place while the menu rolls down, and so we use transition-delay classes.

<nav class="flex flex-col p-8 gap-6">
    <a
        href="#"
        class="nav-link text-lg font-semibold border-b pb-2 opacity-0 -translate-y-4
                     transition-all duration-500 delay-100 text-slate-800">Home
    </a>
    <a
        href="#"
        class="nav-link text-lg font-semibold border-b pb-2 opacity-0 -translate-y-4
                 transition-all duration-500 delay-200ms text-slate-800">Products
    </a>
    <a
        href="#"
        class="nav-link bg-indigo-600 text-white text-center py-4 rounded-xl font-bold opacity-0
                 scale-95 transition-all duration-500 delay-300ms">Sign In
    </a>
</nav>

CodePen (HTML/TailwindCSS)

To see this work in a CodePen click here. Or copy/paste the code below into the HTML body of your page, and add the tailwind CDN.

<!-- import tailwindcss -->
    <script src="https://cdn.tailwindcss.com"></script>
  </head>

  <body class="bg-slate-50">
    <div class="fixed top-0 w-full bg-white shadow-md font-sans z-50"><div class="flex justify-between items-center p-4 border-b bg-white relative z-10">
        <span class="font-bold text-xl text-indigo-600">CakeStack®</span><button id="menu-btn" class="p-2 px-4 bg-neutral-100 hover:bg-neutral-200 rounded-md transition-colors font-medium">Menu</button></div>
        <div id="dropdown-menu" class="grid grid-rows-[0fr] opacity-0 transition-all duration-500 ease-in-out overflow-hidden bg-white border-b shadow-xl">
        <div class="min-h-0"><nav class="flex flex-col p-8 gap-6"><a href="#"  class="nav-link text-lg font-semibold border-b pb-2 opacity-0 -translate-y-4 transition-all duration-500 delay-100 text-slate-800"
            >Home</a>
            <a href="#" class="nav-link bg-indigo-600 text-white text-center py-4 rounded-xl font-bold opacity-0 scale-95 transition-all duration-500 delay-300">Sign In</a>
          </nav></div></div>
        </div>

    <!-- JavaScript  -->
    <script>
      ...
        // Toggle Curtain Height and Opacity
        curtain.classList.toggle("grid-rows-[1fr]", isOpen);
        curtain.classList.toggle("grid-rows-[0fr]", !isOpen);
        curtain.classList.toggle("opacity-100", isOpen);
        curtain.classList.toggle("opacity-0", !isOpen);
      });
    </script>
  </body>
</html>

2. Component Logic with React/NextJS

Ensure the menu wrapper follows this structure:

 import Link from 'next/link';

 <div className={`grid transition-all duration-500 ease-curtain ${isOpen ? 'grid-rows-[1fr] ' +
'   opacity-100'
    : 'grid-rows-[0fr] opacity-0'}`}>
    <div className="overflow-hidden">
        {/* Nav Links with staggered delays */}
        <Link className={`transition-all delay-100 ${isOpen ? 'translate-y-0' : 'translate-y-4'}`}>
        Home</Link>
        <Link className={`transition-all delay-300 ${isOpen ? 'translate-y-0' : 'translate-y-4'}`}>
        Sign In</Link>
    </div>
 </div>

CAKE®STACK

Rolling With The Dough