14 / 20
All 20 in 20 Tailwind CSS Animated Buttons
- 01Tailwind Neon Glow Button with Pulsing Halo
- 02Tailwind Background Slide Hover Button (Four Directions)
- 03Tailwind Ripple Effect Button with No JavaScript
- 04Tailwind Animated Border Draw Button with Staged Delays
- 05Tailwind 3D Press Effect Button with Keycap Depth
- 06Tailwind Liquid Fill Button with Animated Wave Crest
- 07Tailwind Moving Gradient CTA Button
- 08Tailwind Shine Sweep Hover Button with Gloss Streak
- 09Tailwind Jelly Bounce Button with Elastic Squash
- 10Tailwind Text Flip Button Hover with 3D Rotation
- 11Tailwind Animated CTA Hero Button with Beacon Rings & Particle Burst
- 12Tailwind Submit Button Success State with Checkmark Draw
- 13Tailwind Animated Download Button with Progress Fill
- 14Tailwind Hamburger to Close Button with aria-expanded Variants
- 15Tailwind Add to Cart Button with Flying Product Dot
- 16Tailwind Social Share Buttons with Brand Hover & Copy Link
- 17Tailwind Animated Toggle Switch with peer-checked & has-checked
- 18Tailwind Loading Spinner Inside Button with State Machine
- 19Tailwind Next & Prev Arrow Buttons with Chasing Icons
- 20Tailwind Ghost Button with Cursor-Aware Background Reveal
Tailwind Hamburger to Close Button with aria-expanded Variants
Three bars become an ×. The port's win is that the animation now keys off the accessibility attribute the toggle already has to set: group-aria-expanded:rotate-45 reads aria-expanded directly, so the icon state and the announced state cannot drift.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tab-14 w-full min-h-svh bg-tab14-bg font-tab14-sans">
<div id="tab14-shell" class="group" data-open="false">
<div class="flex items-center justify-between px-6 py-4 border-b border-white/8">
<span class="text-[1.1rem] font-bold tracking-[-0.01em] text-tab14-ink">MyApp</span>
<button type="button" id="tab14-btn" aria-label="Toggle navigation" aria-expanded="false" aria-controls="tab14-menu" class="group/btn flex flex-col items-center justify-center gap-1.5 size-11 bg-white/6 border border-white/10 rounded-lg cursor-pointer transition-colors duration-[250ms] hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc aria-expanded:bg-tab14-acc/20 aria-expanded:border-tab14-acc/40">
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[translate,rotate,scale,opacity,background-color] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:translate-y-2 group-aria-expanded/btn:rotate-45 group-aria-expanded/btn:bg-tab14-acc motion-reduce:transition-none"></span>
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[scale,opacity] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:scale-x-0 group-aria-expanded/btn:opacity-0 motion-reduce:transition-none"></span>
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[translate,rotate,scale,opacity,background-color] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:-translate-y-2 group-aria-expanded/btn:-rotate-45 group-aria-expanded/btn:bg-tab14-acc motion-reduce:transition-none"></span>
</button>
</div>
<nav id="tab14-menu" aria-label="Main" class="flex flex-col overflow-hidden max-h-0 px-6 opacity-0 transition-[max-height,opacity,padding] duration-[400ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-data-[open=true]:max-h-52 group-data-[open=true]:opacity-100 group-data-[open=true]:py-4 motion-reduce:transition-none">
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Home</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Features</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Pricing</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Docs</a>
</nav>
</div>
</section><section class="tab-14 w-full min-h-svh bg-tab14-bg font-tab14-sans">
<div id="tab14-shell" class="group" data-open="false">
<div class="flex items-center justify-between px-6 py-4 border-b border-white/8">
<span class="text-[1.1rem] font-bold tracking-[-0.01em] text-tab14-ink">MyApp</span>
<button type="button" id="tab14-btn" aria-label="Toggle navigation" aria-expanded="false" aria-controls="tab14-menu" class="group/btn flex flex-col items-center justify-center gap-1.5 size-11 bg-white/6 border border-white/10 rounded-lg cursor-pointer transition-colors duration-[250ms] hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc aria-expanded:bg-tab14-acc/20 aria-expanded:border-tab14-acc/40">
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[translate,rotate,scale,opacity,background-color] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:translate-y-2 group-aria-expanded/btn:rotate-45 group-aria-expanded/btn:bg-tab14-acc motion-reduce:transition-none"></span>
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[scale,opacity] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:scale-x-0 group-aria-expanded/btn:opacity-0 motion-reduce:transition-none"></span>
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[translate,rotate,scale,opacity,background-color] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:-translate-y-2 group-aria-expanded/btn:-rotate-45 group-aria-expanded/btn:bg-tab14-acc motion-reduce:transition-none"></span>
</button>
</div>
<nav id="tab14-menu" aria-label="Main" class="flex flex-col overflow-hidden max-h-0 px-6 opacity-0 transition-[max-height,opacity,padding] duration-[400ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-data-[open=true]:max-h-52 group-data-[open=true]:opacity-100 group-data-[open=true]:py-4 motion-reduce:transition-none">
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Home</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Features</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Pricing</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Docs</a>
</nav>
</div>
</section>@import "tailwindcss";
@theme {
--color-tab14-bg: #0f172a;
--color-tab14-bg: oklch(0.21 0.04 258);
--color-tab14-ink: #e2e8f0;
--color-tab14-ink: oklch(0.92 0.01 250);
--color-tab14-mut: #94a3b8;
--color-tab14-mut: oklch(0.72 0.03 255);
--color-tab14-acc: #a78bfa;
--color-tab14-acc: oklch(0.74 0.15 292);
--font-tab14-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tab-14 {
width: 100%;
min-height: 100svh;
}
.tab-14 ::selection {
background: var(--color-tab14-acc);
color: #14091f;
}@import "tailwindcss";
@theme {
--color-tab14-bg: #0f172a;
--color-tab14-bg: oklch(0.21 0.04 258);
--color-tab14-ink: #e2e8f0;
--color-tab14-ink: oklch(0.92 0.01 250);
--color-tab14-mut: #94a3b8;
--color-tab14-mut: oklch(0.72 0.03 255);
--color-tab14-acc: #a78bfa;
--color-tab14-acc: oklch(0.74 0.15 292);
--font-tab14-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tab-14 {
width: 100%;
min-height: 100svh;
}
.tab-14 ::selection {
background: var(--color-tab14-acc);
color: #14091f;
}(function () {
var shell = document.getElementById('tab14-shell');
var btn = document.getElementById('tab14-btn');
if (!shell || !btn) return;
btn.addEventListener('click', function () {
var open = btn.getAttribute('aria-expanded') !== 'true';
btn.setAttribute('aria-expanded', String(open));
shell.dataset.open = String(open);
});
})();(function () {
var shell = document.getElementById('tab14-shell');
var btn = document.getElementById('tab14-btn');
if (!shell || !btn) return;
btn.addEventListener('click', function () {
var open = btn.getAttribute('aria-expanded') !== 'true';
btn.setAttribute('aria-expanded', String(open));
shell.dataset.open = String(open);
});
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tab14-bg: #0f172a;
--color-tab14-bg: oklch(0.21 0.04 258);
--color-tab14-ink: #e2e8f0;
--color-tab14-ink: oklch(0.92 0.01 250);
--color-tab14-mut: #94a3b8;
--color-tab14-mut: oklch(0.72 0.03 255);
--color-tab14-acc: #a78bfa;
--color-tab14-acc: oklch(0.74 0.15 292);
--font-tab14-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tab-14 {
width: 100%;
min-height: 100svh;
}
.tab-14 ::selection {
background: var(--color-tab14-acc);
color: #14091f;
}@import "tailwindcss";
@theme {
--color-tab14-bg: #0f172a;
--color-tab14-bg: oklch(0.21 0.04 258);
--color-tab14-ink: #e2e8f0;
--color-tab14-ink: oklch(0.92 0.01 250);
--color-tab14-mut: #94a3b8;
--color-tab14-mut: oklch(0.72 0.03 255);
--color-tab14-acc: #a78bfa;
--color-tab14-acc: oklch(0.74 0.15 292);
--font-tab14-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tab-14 {
width: 100%;
min-height: 100svh;
}
.tab-14 ::selection {
background: var(--color-tab14-acc);
color: #14091f;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
group-aria-expanded/btn:— a named group plus a built-in ARIA variant. The icon is now styled from the same attribute a screen reader reads, which is the strongest form of "style cannot drift from semantics".- The controller sets two attributes instead of toggling two classes, and one of them (
aria-expanded) it had to set anyway. Net cost of the port: one line. - UI change from the CSS original: links are 44px tall and the panel is a real
<nav>witharia-controlswiring. The original's 0.6rem-padded links were about 34px — under the touch minimum.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Hamburger to Close Button with aria-expanded Variants
Source: https://codefronts.com/motion/tailwind-css-animated-buttons/tailwind-hamburger-to-close-button/
Three bars become an ×. The port's win is that the animation now keys off the accessibility attribute the toggle already has to set: group-aria-expanded:rotate-45 reads aria-expanded directly, so the icon state and the announced state cannot drift.
## HTML
```html
<section class="tab-14 w-full min-h-svh bg-tab14-bg font-tab14-sans">
<div id="tab14-shell" class="group" data-open="false">
<div class="flex items-center justify-between px-6 py-4 border-b border-white/8">
<span class="text-[1.1rem] font-bold tracking-[-0.01em] text-tab14-ink">MyApp</span>
<button type="button" id="tab14-btn" aria-label="Toggle navigation" aria-expanded="false" aria-controls="tab14-menu" class="group/btn flex flex-col items-center justify-center gap-1.5 size-11 bg-white/6 border border-white/10 rounded-lg cursor-pointer transition-colors duration-[250ms] hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc aria-expanded:bg-tab14-acc/20 aria-expanded:border-tab14-acc/40">
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[translate,rotate,scale,opacity,background-color] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:translate-y-2 group-aria-expanded/btn:rotate-45 group-aria-expanded/btn:bg-tab14-acc motion-reduce:transition-none"></span>
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[scale,opacity] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:scale-x-0 group-aria-expanded/btn:opacity-0 motion-reduce:transition-none"></span>
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[translate,rotate,scale,opacity,background-color] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:-translate-y-2 group-aria-expanded/btn:-rotate-45 group-aria-expanded/btn:bg-tab14-acc motion-reduce:transition-none"></span>
</button>
</div>
<nav id="tab14-menu" aria-label="Main" class="flex flex-col overflow-hidden max-h-0 px-6 opacity-0 transition-[max-height,opacity,padding] duration-[400ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-data-[open=true]:max-h-52 group-data-[open=true]:opacity-100 group-data-[open=true]:py-4 motion-reduce:transition-none">
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Home</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Features</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Pricing</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Docs</a>
</nav>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tab14-bg: #0f172a;
--color-tab14-bg: oklch(0.21 0.04 258);
--color-tab14-ink: #e2e8f0;
--color-tab14-ink: oklch(0.92 0.01 250);
--color-tab14-mut: #94a3b8;
--color-tab14-mut: oklch(0.72 0.03 255);
--color-tab14-acc: #a78bfa;
--color-tab14-acc: oklch(0.74 0.15 292);
--font-tab14-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tab-14 {
width: 100%;
min-height: 100svh;
}
.tab-14 ::selection {
background: var(--color-tab14-acc);
color: #14091f;
}
```
## JavaScript
```js
(function () {
var shell = document.getElementById('tab14-shell');
var btn = document.getElementById('tab14-btn');
if (!shell || !btn) return;
btn.addEventListener('click', function () {
var open = btn.getAttribute('aria-expanded') !== 'true';
btn.setAttribute('aria-expanded', String(open));
shell.dataset.open = String(open);
});
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Hamburger to Close Button with aria-expanded Variants
Source: https://codefronts.com/motion/tailwind-css-animated-buttons/tailwind-hamburger-to-close-button/
Three bars become an ×. The port's win is that the animation now keys off the accessibility attribute the toggle already has to set: group-aria-expanded:rotate-45 reads aria-expanded directly, so the icon state and the announced state cannot drift.
## HTML
```html
<section class="tab-14 w-full min-h-svh bg-tab14-bg font-tab14-sans">
<div id="tab14-shell" class="group" data-open="false">
<div class="flex items-center justify-between px-6 py-4 border-b border-white/8">
<span class="text-[1.1rem] font-bold tracking-[-0.01em] text-tab14-ink">MyApp</span>
<button type="button" id="tab14-btn" aria-label="Toggle navigation" aria-expanded="false" aria-controls="tab14-menu" class="group/btn flex flex-col items-center justify-center gap-1.5 size-11 bg-white/6 border border-white/10 rounded-lg cursor-pointer transition-colors duration-[250ms] hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc aria-expanded:bg-tab14-acc/20 aria-expanded:border-tab14-acc/40">
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[translate,rotate,scale,opacity,background-color] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:translate-y-2 group-aria-expanded/btn:rotate-45 group-aria-expanded/btn:bg-tab14-acc motion-reduce:transition-none"></span>
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[scale,opacity] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:scale-x-0 group-aria-expanded/btn:opacity-0 motion-reduce:transition-none"></span>
<span class="block w-6 h-0.5 rounded-sm bg-tab14-ink origin-center transition-[translate,rotate,scale,opacity,background-color] duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-aria-expanded/btn:-translate-y-2 group-aria-expanded/btn:-rotate-45 group-aria-expanded/btn:bg-tab14-acc motion-reduce:transition-none"></span>
</button>
</div>
<nav id="tab14-menu" aria-label="Main" class="flex flex-col overflow-hidden max-h-0 px-6 opacity-0 transition-[max-height,opacity,padding] duration-[400ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-data-[open=true]:max-h-52 group-data-[open=true]:opacity-100 group-data-[open=true]:py-4 motion-reduce:transition-none">
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Home</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Features</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline border-b border-white/5 transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Pricing</a>
<a href="#" class="flex items-center min-h-11 text-[0.95rem] font-semibold text-tab14-mut no-underline transition-colors duration-200 hover:text-tab14-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tab14-acc">Docs</a>
</nav>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tab14-bg: #0f172a;
--color-tab14-bg: oklch(0.21 0.04 258);
--color-tab14-ink: #e2e8f0;
--color-tab14-ink: oklch(0.92 0.01 250);
--color-tab14-mut: #94a3b8;
--color-tab14-mut: oklch(0.72 0.03 255);
--color-tab14-acc: #a78bfa;
--color-tab14-acc: oklch(0.74 0.15 292);
--font-tab14-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tab-14 {
width: 100%;
min-height: 100svh;
}
.tab-14 ::selection {
background: var(--color-tab14-acc);
color: #14091f;
}
```
## JavaScript
```js
(function () {
var shell = document.getElementById('tab14-shell');
var btn = document.getElementById('tab14-btn');
if (!shell || !btn) return;
btn.addEventListener('click', function () {
var open = btn.getAttribute('aria-expanded') !== 'true';
btn.setAttribute('aria-expanded', String(open));
shell.dataset.open = String(open);
});
})();
```How this works
The attribute is the state. A toggle must set aria-expanded for assistive tech regardless of styling. The CSS version then set .is-open as well — two sources of truth for one fact. Tailwind's aria-expanded: variant lets the bars read the attribute itself, so the controller sets one thing.
Bars are spans, not an SVG swap. The top bar drops 8px and rotates 45°, the bottom rises 8px and rotates -45°, the middle scales to nothing. The offsets are exactly the bar gap, which is why the cross closes cleanly at any bar width.
The panel is still a max-height transition, which needs a concrete open value (max-h-52). The wrapper carries data-open so the panel and the button can both be styled from one ancestor without either reaching into the other.
Make it yours
- Bolder icon: widen the bars (
w-6→w-7) and keep the translate equal to the gap. - Spin the whole button on open with
aria-expanded:rotate-90on the button itself. - Swap the cross for a back-arrow by giving the top bar a shorter
scale-xand a different rotation.
Gotchas — read before shipping
- The translate must equal the distance between bar centres. Change
gap-1.5and the cross stops meeting at the middle. max-h-52has to exceed the panel's natural height or the last link clips; there is nomax-h-autothat animates.- Do not use
aria-expandedon the wrapper as well — only the control that expands something may carry it, which is why the panel keys offdata-openinstead.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Transform transitions and ARIA attribute selectors are universally supported; the floor is Tailwind v4's generated sheet.