19 / 28
All 28 in 28 Tailwind Hamburger Menus
- 01Tailwind Hamburger Menu with Clip-Path Circle Expansion
- 02Tailwind Keyboard Navigable Hamburger Menu with ARIA Attributes
- 03Tailwind Animated 3-Line to X Hamburger Icon Toggle
- 04Tailwind Slide-In Off-Canvas Drawer with Dimmed Backdrop Overlay
- 05Pure Tailwind Checkbox Hack Hamburger Navigation (No JavaScript)
- 06Tailwind Responsive Hamburger Menu with Integrated Search Bar & Social Links
- 07Tailwind Glassmorphism Backdrop Blur Hamburger Navigation
- 08Tailwind Push Page Navigation Menu (Off-Canvas Content Shift)
- 09Pure Tailwind Hamburger Menu Without JavaScript
- 10Tailwind Hamburger Menu Morphing to Close Icon Animation
- 11Tailwind Full Screen Mobile Navigation Overlay
- 12Tailwind Slide Out Sidebar Navigation / Off Canvas Menu
- 13Tailwind Responsive Navbar Mobile Hamburger Toggle Breakpoints
- 14Tailwind Minimalist Floating Hamburger Button
- 15Tailwind CSS Responsive Hamburger Menu Component
- 16Tailwind Rebuild of the Bootstrap 5 Navbar Hamburger Toggle
- 17Tailwind WCAG Accessible Hamburger Menu Example
- 18Pure Tailwind Dropdown Slide Down Mobile Hamburger Menu
- 19Simple 3-Line Tailwind Hamburger Icon Hover Effect
- 20Tailwind Full Screen Blur Overlay Hamburger Menu
- 21Tailwind Mobile Bottom Navigation Bar with Floating Hamburger Toggle
- 22Pure Tailwind Multi-Level Accordion Hamburger Dropdown Menu
- 23Flexbox Navbar Right-Aligned Tailwind Hamburger Icon
- 24Radial Ripple Expand Tailwind Hamburger Menu
- 25Sticky Tailwind Navbar Shrinking Hamburger Icon on Scroll
- 26Neumorphic Tailwind Hamburger Toggle Button
- 27Brutalist Design Bordered Tailwind Hamburger Menu
- 28Tailwind 3D Flip Canvas Mobile Hamburger Navigation
Simple 3-Line Tailwind Hamburger Icon Hover Effect
Five hover treatments for the resting burger — the micro-interaction users see a hundred times before they ever click. The port's real contribution is enforcement: every group-hover: has a group-focus-visible: twin typed immediately after it, so the keyboard gap that hover-only effects always ship is impossible to miss here.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="thb-19 w-full min-h-svh bg-thb19-bg font-thb19-sans text-thb19-ink px-5 py-9" aria-label="Hamburger icon hover effects demo">
<div class="p-6 rounded-[20px] border border-thb19-edge bg-thb19-bg">
<header class="flex items-baseline justify-between gap-3 flex-wrap mb-5">
<h2 class="text-base font-semibold tracking-[-0.01em]">Hover states</h2>
<span class="text-[11.5px] text-thb19-mut">hover or Tab through — resting-state micro-interactions</span>
</header>
<div class="grid grid-cols-[repeat(auto-fit,minmax(min(118px,100%),1fr))] gap-3.25">
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Slide-through hover demo" class="group w-full aspect-[1.15] grid place-items-center overflow-hidden rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5 overflow-hidden" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut delay-[45ms] group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut delay-[90ms] group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Slide-through</b><code class="font-thb19-mono text-[10px] text-thb19-mut">mask + after: twin</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Stretch hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[8.75px] w-[17px] h-[2.5px] rounded-sm bg-current transition-[width] duration-300 ease-thb19 group-hover:w-7 group-focus-visible:w-7 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Stretch</b><code class="font-thb19-mono text-[10px] text-thb19-mut">middle 17px → 28px</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Wave hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce delay-[50ms] group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce delay-[100ms] group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Wave</b><code class="font-thb19-mono text-[10px] text-thb19-mut">staggered dip + bounce</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Spread hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19 group-hover:-translate-y-[3px] group-focus-visible:-translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19 group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Spread</b><code class="font-thb19-mono text-[10px] text-thb19-mut">outer lines ±3px</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Tilt hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5 transition-[rotate] duration-450 ease-thb19bounce group-hover:rotate-90 group-focus-visible:rotate-90 motion-reduce:transition-none" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Tilt</b><code class="font-thb19-mono text-[10px] text-thb19-mut">rotate 90° overshoot</code></figcaption>
</figure>
</div>
</div>
</section><section class="thb-19 w-full min-h-svh bg-thb19-bg font-thb19-sans text-thb19-ink px-5 py-9" aria-label="Hamburger icon hover effects demo">
<div class="p-6 rounded-[20px] border border-thb19-edge bg-thb19-bg">
<header class="flex items-baseline justify-between gap-3 flex-wrap mb-5">
<h2 class="text-base font-semibold tracking-[-0.01em]">Hover states</h2>
<span class="text-[11.5px] text-thb19-mut">hover or Tab through — resting-state micro-interactions</span>
</header>
<div class="grid grid-cols-[repeat(auto-fit,minmax(min(118px,100%),1fr))] gap-3.25">
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Slide-through hover demo" class="group w-full aspect-[1.15] grid place-items-center overflow-hidden rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5 overflow-hidden" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut delay-[45ms] group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut delay-[90ms] group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Slide-through</b><code class="font-thb19-mono text-[10px] text-thb19-mut">mask + after: twin</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Stretch hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[8.75px] w-[17px] h-[2.5px] rounded-sm bg-current transition-[width] duration-300 ease-thb19 group-hover:w-7 group-focus-visible:w-7 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Stretch</b><code class="font-thb19-mono text-[10px] text-thb19-mut">middle 17px → 28px</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Wave hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce delay-[50ms] group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce delay-[100ms] group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Wave</b><code class="font-thb19-mono text-[10px] text-thb19-mut">staggered dip + bounce</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Spread hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19 group-hover:-translate-y-[3px] group-focus-visible:-translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19 group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Spread</b><code class="font-thb19-mono text-[10px] text-thb19-mut">outer lines ±3px</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Tilt hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5 transition-[rotate] duration-450 ease-thb19bounce group-hover:rotate-90 group-focus-visible:rotate-90 motion-reduce:transition-none" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Tilt</b><code class="font-thb19-mono text-[10px] text-thb19-mut">rotate 90° overshoot</code></figcaption>
</figure>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-thb19-bg: #f4f5f8;
--color-thb19-bg: oklch(0.965 0.005 250);
--color-thb19-card: #ffffff;
--color-thb19-card: oklch(1 0 0);
--color-thb19-edge: #e1e3ea;
--color-thb19-edge: oklch(0.89 0.008 250);
--color-thb19-ink: #32354a;
--color-thb19-ink: oklch(0.24 0.015 255);
--color-thb19-mut: #73778a;
--color-thb19-mut: oklch(0.54 0.012 255);
--color-thb19-acc: #d4485a;
--color-thb19-acc: oklch(0.6 0.17 20);
--font-thb19-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb19-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb19: cubic-bezier(0.2, 0.7, 0.2, 1);
--ease-thb19cut: cubic-bezier(0.65, 0, 0.35, 1);
--ease-thb19bounce: cubic-bezier(0.3, 1.6, 0.4, 1);
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-19 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-thb19-bg: #f4f5f8;
--color-thb19-bg: oklch(0.965 0.005 250);
--color-thb19-card: #ffffff;
--color-thb19-card: oklch(1 0 0);
--color-thb19-edge: #e1e3ea;
--color-thb19-edge: oklch(0.89 0.008 250);
--color-thb19-ink: #32354a;
--color-thb19-ink: oklch(0.24 0.015 255);
--color-thb19-mut: #73778a;
--color-thb19-mut: oklch(0.54 0.012 255);
--color-thb19-acc: #d4485a;
--color-thb19-acc: oklch(0.6 0.17 20);
--font-thb19-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb19-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb19: cubic-bezier(0.2, 0.7, 0.2, 1);
--ease-thb19cut: cubic-bezier(0.65, 0, 0.35, 1);
--ease-thb19bounce: cubic-bezier(0.3, 1.6, 0.4, 1);
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-19 {
width: 100%;
min-height: 100svh;
}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-thb19-bg: #f4f5f8;
--color-thb19-bg: oklch(0.965 0.005 250);
--color-thb19-card: #ffffff;
--color-thb19-card: oklch(1 0 0);
--color-thb19-edge: #e1e3ea;
--color-thb19-edge: oklch(0.89 0.008 250);
--color-thb19-ink: #32354a;
--color-thb19-ink: oklch(0.24 0.015 255);
--color-thb19-mut: #73778a;
--color-thb19-mut: oklch(0.54 0.012 255);
--color-thb19-acc: #d4485a;
--color-thb19-acc: oklch(0.6 0.17 20);
--font-thb19-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb19-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb19: cubic-bezier(0.2, 0.7, 0.2, 1);
--ease-thb19cut: cubic-bezier(0.65, 0, 0.35, 1);
--ease-thb19bounce: cubic-bezier(0.3, 1.6, 0.4, 1);
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-19 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-thb19-bg: #f4f5f8;
--color-thb19-bg: oklch(0.965 0.005 250);
--color-thb19-card: #ffffff;
--color-thb19-card: oklch(1 0 0);
--color-thb19-edge: #e1e3ea;
--color-thb19-edge: oklch(0.89 0.008 250);
--color-thb19-ink: #32354a;
--color-thb19-ink: oklch(0.24 0.015 255);
--color-thb19-mut: #73778a;
--color-thb19-mut: oklch(0.54 0.012 255);
--color-thb19-acc: #d4485a;
--color-thb19-acc: oklch(0.6 0.17 20);
--font-thb19-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb19-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb19: cubic-bezier(0.2, 0.7, 0.2, 1);
--ease-thb19cut: cubic-bezier(0.65, 0, 0.35, 1);
--ease-thb19bounce: cubic-bezier(0.3, 1.6, 0.4, 1);
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-19 {
width: 100%;
min-height: 100svh;
}<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&family=JetBrains+Mono:wght@400;500;600&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&family=JetBrains+Mono:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Five variant classes disappear. Each tile's bars carry their own hover behaviour, so shipping an effect means copying one element rather than porting a stylesheet block.
- The
::aftertwin stays a pseudo-element — viaafter:content-[''] after:bg-inherit— and this is the one place in the collection where that is clearly right: the twin is pure decoration, must inherit its parent's paint, and moving it with the bar is the whole trick. :hover, :focus-visiblecomma selectors become adjacentgroup-hover:/group-focus-visible:utilities. The keyboard twin is now a visible part of the class list instead of something a reviewer has to notice missing.- Three
transition-delayrules become threedelay-[45ms]-style utilities on the bars they belong to. - Two easing curves become
@themetokens, so “how bouncy” and “how sharp” are one line each and shared across variants. - Zero JavaScript — these are CSS states of real buttons, and the buttons still click.
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: Simple 3-Line Tailwind Hamburger Icon Hover Effect
Source: https://codefronts.com/navigation/tailwind-hamburger-menus/simple-3-line-tailwind-hamburger-icon-hover-effect/
Five hover treatments for the resting burger — the micro-interaction users see a hundred times before they ever click. The port's real contribution is enforcement: every group-hover: has a group-focus-visible: twin typed immediately after it, so the keyboard gap that hover-only effects always ship is impossible to miss here.
## HTML
```html
<section class="thb-19 w-full min-h-svh bg-thb19-bg font-thb19-sans text-thb19-ink px-5 py-9" aria-label="Hamburger icon hover effects demo">
<div class="p-6 rounded-[20px] border border-thb19-edge bg-thb19-bg">
<header class="flex items-baseline justify-between gap-3 flex-wrap mb-5">
<h2 class="text-base font-semibold tracking-[-0.01em]">Hover states</h2>
<span class="text-[11.5px] text-thb19-mut">hover or Tab through — resting-state micro-interactions</span>
</header>
<div class="grid grid-cols-[repeat(auto-fit,minmax(min(118px,100%),1fr))] gap-3.25">
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Slide-through hover demo" class="group w-full aspect-[1.15] grid place-items-center overflow-hidden rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5 overflow-hidden" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut delay-[45ms] group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut delay-[90ms] group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Slide-through</b><code class="font-thb19-mono text-[10px] text-thb19-mut">mask + after: twin</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Stretch hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[8.75px] w-[17px] h-[2.5px] rounded-sm bg-current transition-[width] duration-300 ease-thb19 group-hover:w-7 group-focus-visible:w-7 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Stretch</b><code class="font-thb19-mono text-[10px] text-thb19-mut">middle 17px → 28px</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Wave hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce delay-[50ms] group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce delay-[100ms] group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Wave</b><code class="font-thb19-mono text-[10px] text-thb19-mut">staggered dip + bounce</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Spread hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19 group-hover:-translate-y-[3px] group-focus-visible:-translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19 group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Spread</b><code class="font-thb19-mono text-[10px] text-thb19-mut">outer lines ±3px</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Tilt hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5 transition-[rotate] duration-450 ease-thb19bounce group-hover:rotate-90 group-focus-visible:rotate-90 motion-reduce:transition-none" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Tilt</b><code class="font-thb19-mono text-[10px] text-thb19-mut">rotate 90° overshoot</code></figcaption>
</figure>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-thb19-bg: #f4f5f8;
--color-thb19-bg: oklch(0.965 0.005 250);
--color-thb19-card: #ffffff;
--color-thb19-card: oklch(1 0 0);
--color-thb19-edge: #e1e3ea;
--color-thb19-edge: oklch(0.89 0.008 250);
--color-thb19-ink: #32354a;
--color-thb19-ink: oklch(0.24 0.015 255);
--color-thb19-mut: #73778a;
--color-thb19-mut: oklch(0.54 0.012 255);
--color-thb19-acc: #d4485a;
--color-thb19-acc: oklch(0.6 0.17 20);
--font-thb19-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb19-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb19: cubic-bezier(0.2, 0.7, 0.2, 1);
--ease-thb19cut: cubic-bezier(0.65, 0, 0.35, 1);
--ease-thb19bounce: cubic-bezier(0.3, 1.6, 0.4, 1);
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-19 {
width: 100%;
min-height: 100svh;
}
```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: Simple 3-Line Tailwind Hamburger Icon Hover Effect
Source: https://codefronts.com/navigation/tailwind-hamburger-menus/simple-3-line-tailwind-hamburger-icon-hover-effect/
Five hover treatments for the resting burger — the micro-interaction users see a hundred times before they ever click. The port's real contribution is enforcement: every group-hover: has a group-focus-visible: twin typed immediately after it, so the keyboard gap that hover-only effects always ship is impossible to miss here.
## HTML
```html
<section class="thb-19 w-full min-h-svh bg-thb19-bg font-thb19-sans text-thb19-ink px-5 py-9" aria-label="Hamburger icon hover effects demo">
<div class="p-6 rounded-[20px] border border-thb19-edge bg-thb19-bg">
<header class="flex items-baseline justify-between gap-3 flex-wrap mb-5">
<h2 class="text-base font-semibold tracking-[-0.01em]">Hover states</h2>
<span class="text-[11.5px] text-thb19-mut">hover or Tab through — resting-state micro-interactions</span>
</header>
<div class="grid grid-cols-[repeat(auto-fit,minmax(min(118px,100%),1fr))] gap-3.25">
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Slide-through hover demo" class="group w-full aspect-[1.15] grid place-items-center overflow-hidden rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5 overflow-hidden" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut delay-[45ms] group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current after:content-[''] after:absolute after:inset-0 after:rounded-sm after:bg-inherit after:-translate-x-[34px] transition-[translate] duration-350 ease-thb19cut delay-[90ms] group-hover:translate-x-[34px] group-focus-visible:translate-x-[34px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Slide-through</b><code class="font-thb19-mono text-[10px] text-thb19-mut">mask + after: twin</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Stretch hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[8.75px] w-[17px] h-[2.5px] rounded-sm bg-current transition-[width] duration-300 ease-thb19 group-hover:w-7 group-focus-visible:w-7 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Stretch</b><code class="font-thb19-mono text-[10px] text-thb19-mut">middle 17px → 28px</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Wave hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce delay-[50ms] group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19bounce delay-[100ms] group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Wave</b><code class="font-thb19-mono text-[10px] text-thb19-mut">staggered dip + bounce</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Spread hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19 group-hover:-translate-y-[3px] group-focus-visible:-translate-y-[3px] motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current transition-[translate] duration-300 ease-thb19 group-hover:translate-y-[3px] group-focus-visible:translate-y-[3px] motion-reduce:transition-none"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Spread</b><code class="font-thb19-mono text-[10px] text-thb19-mut">outer lines ±3px</code></figcaption>
</figure>
<figure class="flex flex-col items-center gap-2.75">
<button type="button" aria-label="Tilt hover demo" class="group w-full aspect-[1.15] grid place-items-center rounded-[15px] border border-thb19-edge bg-thb19-card text-thb19-ink cursor-pointer shadow-[0_10px_24px_-20px_oklch(0.3_0.03_255/0.7)] transition-[border-color,translate] duration-250 hover:-translate-y-0.5 hover:border-[color-mix(in_oklch,var(--color-thb19-acc)_45%,var(--color-thb19-edge))] focus-visible:outline-2 focus-visible:outline-thb19-acc focus-visible:outline-offset-2">
<span class="relative block w-7 h-5 transition-[rotate] duration-450 ease-thb19bounce group-hover:rotate-90 group-focus-visible:rotate-90 motion-reduce:transition-none" aria-hidden="true">
<i class="absolute left-0 top-0 w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[8.75px] w-7 h-[2.5px] rounded-sm bg-current"></i>
<i class="absolute left-0 top-[17.5px] w-7 h-[2.5px] rounded-sm bg-current"></i>
</span>
</button>
<figcaption class="grid justify-items-center gap-1 text-center"><b class="text-[12.5px] font-semibold">Tilt</b><code class="font-thb19-mono text-[10px] text-thb19-mut">rotate 90° overshoot</code></figcaption>
</figure>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-thb19-bg: #f4f5f8;
--color-thb19-bg: oklch(0.965 0.005 250);
--color-thb19-card: #ffffff;
--color-thb19-card: oklch(1 0 0);
--color-thb19-edge: #e1e3ea;
--color-thb19-edge: oklch(0.89 0.008 250);
--color-thb19-ink: #32354a;
--color-thb19-ink: oklch(0.24 0.015 255);
--color-thb19-mut: #73778a;
--color-thb19-mut: oklch(0.54 0.012 255);
--color-thb19-acc: #d4485a;
--color-thb19-acc: oklch(0.6 0.17 20);
--font-thb19-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb19-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb19: cubic-bezier(0.2, 0.7, 0.2, 1);
--ease-thb19cut: cubic-bezier(0.65, 0, 0.35, 1);
--ease-thb19bounce: cubic-bezier(0.3, 1.6, 0.4, 1);
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-19 {
width: 100%;
min-height: 100svh;
}
```How this works
Slide-through is the showpiece. Each bar is doubled — the visible bar plus a twin parked one icon-width to the left — inside an overflow mask; hover slides the pair one full width right, so the bar appears to leave and immediately re-arrive:
<span class="overflow-hidden">
<i class="after:content-[''] after:absolute after:inset-0
after:-translate-x-[34px] after:bg-inherit
transition-[translate] duration-350 ease-thb19
group-hover:translate-x-[34px]
group-focus-visible:translate-x-[34px]"></i>
</span>Because bar and twin move as one element (the twin IS its ::after), there is exactly one transition to time. The 45ms cascade does the rest.
The other four, each one dial. Stretch preloads meaning — the middle bar sits at 17px and grows to full width, saying “there is more here”. Wave is three staggered dips on an overshoot curve so the bars bounce back past rest. Spread moves only the outer bars ±3px, the lowest-key tell that an element is live. Tilt rotates the whole icon 90° on the same overshoot curve — the bounce IS the personality, and it costs one utility.
Why this is the port's accessibility demo. In CSS a hover effect is one selector and its focus twin is a comma away — easy to omit, invisible in review. Here the two variants sit adjacent in the class list, so a missing group-focus-visible: is a visible hole. Every effect is transform-only, and every one collapses under motion-reduce:.
Make it yours
- All five share the geometry: a
w-7 h-5box andh-[2.5px]bars. Restyle once and every variant follows. - Slide-through direction: negate both translates for right-to-left. A vertical slide works by masking height instead of width.
- Stagger tempo: 45ms between bars is the sweet spot. Over 80ms reads as broken; 0 reads as one blob.
- Bounce intensity lives in
--ease-thb19bounce; raise the third control point for more overshoot. - These are RESTING states — pair any of them with a demo 03 morph for the open state. The two state machines (hover vs
aria-pressed) never conflict.
Gotchas — read before shipping
- The twin must be the bar's own
after:layer, not a second element in the mask. Separate elements need separate transitions and drift out of sync under load. after:bg-inheritonly works if the bar itself carries a background — give bars a solidbg-current, never a gradient on the button.overflow-hiddenon the button clips focus rings in some engines. Keepfocus-visible:outline-offset-2positive so the ring sits outside the clip.- Write the
group-focus-visible:twin for EVERYgroup-hover:. A hover-only micro-interaction is a silent accessibility gap — felt by keyboard users, invisible in most audits. delay-*applies to all transitioned properties on that element. These bars transition one property each, which is why the hand-cut cascade is safe here.- Hover effects are free on desktop and silently absent on touch. That is correct degradation only because hover is never the sole affordance — these are resting states of a button that still clicks.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Transitions and pseudo-element twins run in anything from the last decade, and :focus-visible has been Baseline since 2022. The floor quoted here is Tailwind v4's own generated sheet.