28 Tailwind Hamburger Menus
26 / 28

All 28 in 28 Tailwind Hamburger Menus

  1. 01Tailwind Hamburger Menu with Clip-Path Circle Expansion
  2. 02Tailwind Keyboard Navigable Hamburger Menu with ARIA Attributes
  3. 03Tailwind Animated 3-Line to X Hamburger Icon Toggle
  4. 04Tailwind Slide-In Off-Canvas Drawer with Dimmed Backdrop Overlay
  5. 05Pure Tailwind Checkbox Hack Hamburger Navigation (No JavaScript)
  6. 06Tailwind Responsive Hamburger Menu with Integrated Search Bar & Social Links
  7. 07Tailwind Glassmorphism Backdrop Blur Hamburger Navigation
  8. 08Tailwind Push Page Navigation Menu (Off-Canvas Content Shift)
  9. 09Pure Tailwind Hamburger Menu Without JavaScript
  10. 10Tailwind Hamburger Menu Morphing to Close Icon Animation
  11. 11Tailwind Full Screen Mobile Navigation Overlay
  12. 12Tailwind Slide Out Sidebar Navigation / Off Canvas Menu
  13. 13Tailwind Responsive Navbar Mobile Hamburger Toggle Breakpoints
  14. 14Tailwind Minimalist Floating Hamburger Button
  15. 15Tailwind CSS Responsive Hamburger Menu Component
  16. 16Tailwind Rebuild of the Bootstrap 5 Navbar Hamburger Toggle
  17. 17Tailwind WCAG Accessible Hamburger Menu Example
  18. 18Pure Tailwind Dropdown Slide Down Mobile Hamburger Menu
  19. 19Simple 3-Line Tailwind Hamburger Icon Hover Effect
  20. 20Tailwind Full Screen Blur Overlay Hamburger Menu
  21. 21Tailwind Mobile Bottom Navigation Bar with Floating Hamburger Toggle
  22. 22Pure Tailwind Multi-Level Accordion Hamburger Dropdown Menu
  23. 23Flexbox Navbar Right-Aligned Tailwind Hamburger Icon
  24. 24Radial Ripple Expand Tailwind Hamburger Menu
  25. 25Sticky Tailwind Navbar Shrinking Hamburger Icon on Scroll
  26. 26Neumorphic Tailwind Hamburger Toggle Button
  27. 27Brutalist Design Bordered Tailwind Hamburger Menu
  28. 28Tailwind 3D Flip Canvas Mobile Hamburger Navigation
Pure TailwindMIT licensed

Neumorphic Tailwind Hamburger Toggle Button

Soft-UI physics done by the rules: the burger is a clay button raised by the canonical dual shadow, and pressing it does not move it — it inverts the shadows to inset, so the button sinks INTO the surface. In utilities both shadow states sit in one class list, which makes the light-source discipline that neumorphism lives by impossible to get wrong by accident.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="thb-26 w-full min-h-svh bg-thb26-clay font-thb26-sans text-thb26-ink px-5 py-9" aria-label="Neumorphic hamburger toggle demo">
  <div class="group relative min-h-120 flex flex-col items-center gap-6.5 px-7 py-8.5 rounded-[22px] bg-thb26-clay border border-thb26-edge">
    <input type="checkbox" id="thb26-t" class="peer absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">
    <div class="flex flex-col items-center gap-3.5">
      <label for="thb26-t" class="grid place-items-center w-18.5 h-18.5 rounded-[22px] bg-thb26-clay cursor-pointer select-none shadow-[-7px_-7px_14px_var(--color-thb26-lite),7px_7px_14px_var(--color-thb26-shade)] transition-[box-shadow] duration-300 hover:shadow-[-9px_-9px_18px_var(--color-thb26-lite),9px_9px_18px_var(--color-thb26-shade)] peer-focus-visible:outline-3 peer-focus-visible:outline-thb26-acc peer-focus-visible:outline-offset-3 group-has-checked:shadow-[inset_-5px_-5px_10px_var(--color-thb26-lite),inset_5px_5px_10px_var(--color-thb26-shade)]">
        <span class="relative block w-6 h-4" aria-hidden="true">
          <i class="absolute left-0 top-0 w-6 h-[2.6px] rounded-[3px] bg-thb26-ink transition-[translate,rotate,background] duration-300 ease-thb26 group-has-checked:translate-y-[6.7px] group-has-checked:rotate-45 group-has-checked:bg-thb26-acc motion-reduce:transition-none"></i>
          <i class="absolute left-0 top-[6.7px] w-6 h-[2.6px] rounded-[3px] bg-thb26-ink transition-[opacity,scale,background] duration-250 ease-thb26 group-has-checked:opacity-0 group-has-checked:scale-x-40 group-has-checked:bg-thb26-acc motion-reduce:transition-none"></i>
          <i class="absolute left-0 top-[13.4px] w-6 h-[2.6px] rounded-[3px] bg-thb26-ink transition-[translate,rotate,background] duration-300 ease-thb26 group-has-checked:-translate-y-[6.7px] group-has-checked:-rotate-45 group-has-checked:bg-thb26-acc motion-reduce:transition-none"></i>
        </span>
      </label>
      <span class="relative block h-4 overflow-hidden font-thb26-mono text-[11px] tracking-[0.22em] uppercase text-thb26-mut" aria-hidden="true">
        <b class="block h-4 leading-4 font-bold transition-[translate] duration-350 ease-thb26 group-has-checked:-translate-y-4">rest</b>
        <b class="block h-4 leading-4 font-bold transition-[translate] duration-350 ease-thb26 group-has-checked:-translate-y-4">pressed</b>
      </span>
    </div>
    <nav aria-label="Menu" class="grid gap-3.5 w-[min(280px,100%)] p-1.5">
      <a href="#home" style="--i:0" class="group/p flex items-center gap-3.25 min-h-13 px-5 rounded-2xl bg-thb26-clay text-thb26-ink text-sm font-bold no-underline shadow-[-6px_-6px_12px_var(--color-thb26-lite),6px_6px_12px_var(--color-thb26-shade)] opacity-0 translate-y-3 pointer-events-none transition-[box-shadow,opacity,translate,color] duration-350 ease-thb26 hover:text-thb26-acc focus-visible:outline-3 focus-visible:outline-thb26-acc focus-visible:outline-offset-2 active:shadow-[inset_-4px_-4px_8px_var(--color-thb26-lite),inset_4px_4px_8px_var(--color-thb26-shade)] group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:pointer-events-auto group-has-checked:[transition-delay:calc(80ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">
        <i class="w-2 h-2 rounded-full bg-thb26-clay shadow-[inset_-2px_-2px_4px_var(--color-thb26-lite),inset_2px_2px_4px_var(--color-thb26-shade)] transition-[background,box-shadow] duration-200 group-hover/p:bg-thb26-acc group-hover/p:shadow-none group-focus-visible/p:bg-thb26-acc group-focus-visible/p:shadow-none" aria-hidden="true"></i>Home
      </a>
      <a href="#library" style="--i:1" class="group/p flex items-center gap-3.25 min-h-13 px-5 rounded-2xl bg-thb26-clay text-thb26-ink text-sm font-bold no-underline shadow-[-6px_-6px_12px_var(--color-thb26-lite),6px_6px_12px_var(--color-thb26-shade)] opacity-0 translate-y-3 pointer-events-none transition-[box-shadow,opacity,translate,color] duration-350 ease-thb26 hover:text-thb26-acc focus-visible:outline-3 focus-visible:outline-thb26-acc focus-visible:outline-offset-2 active:shadow-[inset_-4px_-4px_8px_var(--color-thb26-lite),inset_4px_4px_8px_var(--color-thb26-shade)] group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:pointer-events-auto group-has-checked:[transition-delay:calc(80ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">
        <i class="w-2 h-2 rounded-full bg-thb26-clay shadow-[inset_-2px_-2px_4px_var(--color-thb26-lite),inset_2px_2px_4px_var(--color-thb26-shade)] transition-[background,box-shadow] duration-200 group-hover/p:bg-thb26-acc group-hover/p:shadow-none group-focus-visible/p:bg-thb26-acc group-focus-visible/p:shadow-none" aria-hidden="true"></i>Library
      </a>
      <a href="#stats" style="--i:2" class="group/p flex items-center gap-3.25 min-h-13 px-5 rounded-2xl bg-thb26-clay text-thb26-ink text-sm font-bold no-underline shadow-[-6px_-6px_12px_var(--color-thb26-lite),6px_6px_12px_var(--color-thb26-shade)] opacity-0 translate-y-3 pointer-events-none transition-[box-shadow,opacity,translate,color] duration-350 ease-thb26 hover:text-thb26-acc focus-visible:outline-3 focus-visible:outline-thb26-acc focus-visible:outline-offset-2 active:shadow-[inset_-4px_-4px_8px_var(--color-thb26-lite),inset_4px_4px_8px_var(--color-thb26-shade)] group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:pointer-events-auto group-has-checked:[transition-delay:calc(80ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">
        <i class="w-2 h-2 rounded-full bg-thb26-clay shadow-[inset_-2px_-2px_4px_var(--color-thb26-lite),inset_2px_2px_4px_var(--color-thb26-shade)] transition-[background,box-shadow] duration-200 group-hover/p:bg-thb26-acc group-hover/p:shadow-none group-focus-visible/p:bg-thb26-acc group-focus-visible/p:shadow-none" aria-hidden="true"></i>Statistics
      </a>
      <a href="#settings" style="--i:3" class="group/p flex items-center gap-3.25 min-h-13 px-5 rounded-2xl bg-thb26-clay text-thb26-ink text-sm font-bold no-underline shadow-[-6px_-6px_12px_var(--color-thb26-lite),6px_6px_12px_var(--color-thb26-shade)] opacity-0 translate-y-3 pointer-events-none transition-[box-shadow,opacity,translate,color] duration-350 ease-thb26 hover:text-thb26-acc focus-visible:outline-3 focus-visible:outline-thb26-acc focus-visible:outline-offset-2 active:shadow-[inset_-4px_-4px_8px_var(--color-thb26-lite),inset_4px_4px_8px_var(--color-thb26-shade)] group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:pointer-events-auto group-has-checked:[transition-delay:calc(80ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">
        <i class="w-2 h-2 rounded-full bg-thb26-clay shadow-[inset_-2px_-2px_4px_var(--color-thb26-lite),inset_2px_2px_4px_var(--color-thb26-shade)] transition-[background,box-shadow] duration-200 group-hover/p:bg-thb26-acc group-hover/p:shadow-none group-focus-visible/p:bg-thb26-acc group-focus-visible/p:shadow-none" aria-hidden="true"></i>Settings
      </a>
    </nav>
    <p class="mt-auto max-w-[34ch] text-center text-xs leading-relaxed text-thb26-mut text-pretty" aria-hidden="true">same clay, two shadows &mdash; pressed = the pair goes <code class="font-thb26-mono text-[11px] px-1.5 py-0.5 rounded bg-thb26-code">inset</code>. Note the focus ring stays loud on purpose.</p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-thb26-clay: #e6e8ef;
  --color-thb26-clay: oklch(0.92 0.012 260);
  --color-thb26-lite: #fbfcff;
  --color-thb26-lite: oklch(0.99 0.005 260);
  --color-thb26-shade: #bcc0cd;
  --color-thb26-shade: oklch(0.78 0.02 260);
  --color-thb26-edge: #d5d8e2;
  --color-thb26-edge: oklch(0.87 0.014 260);
  --color-thb26-code: #d9dce6;
  --color-thb26-code: oklch(0.88 0.014 260);
  --color-thb26-ink: #454a5c;
  --color-thb26-ink: oklch(0.32 0.02 262);
  --color-thb26-mut: #6f7488;
  --color-thb26-mut: oklch(0.52 0.018 262);
  --color-thb26-acc: #3fa8b4;
  --color-thb26-acc: oklch(0.65 0.11 190);
  --font-thb26-sans: 'Space Grotesk', system-ui, sans-serif;
  --font-thb26-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-thb26: cubic-bezier(0.2, 0.7, 0.2, 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-26 {
  width: 100%;
  min-height: 100svh;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-thb26-clay: #e6e8ef;
  --color-thb26-clay: oklch(0.92 0.012 260);
  --color-thb26-lite: #fbfcff;
  --color-thb26-lite: oklch(0.99 0.005 260);
  --color-thb26-shade: #bcc0cd;
  --color-thb26-shade: oklch(0.78 0.02 260);
  --color-thb26-edge: #d5d8e2;
  --color-thb26-edge: oklch(0.87 0.014 260);
  --color-thb26-code: #d9dce6;
  --color-thb26-code: oklch(0.88 0.014 260);
  --color-thb26-ink: #454a5c;
  --color-thb26-ink: oklch(0.32 0.02 262);
  --color-thb26-mut: #6f7488;
  --color-thb26-mut: oklch(0.52 0.018 262);
  --color-thb26-acc: #3fa8b4;
  --color-thb26-acc: oklch(0.65 0.11 190);
  --font-thb26-sans: 'Space Grotesk', system-ui, sans-serif;
  --font-thb26-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-thb26: cubic-bezier(0.2, 0.7, 0.2, 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-26 {
  width: 100%;
  min-height: 100svh;
}
3Fonts used by this demooptional — skip if you have your own type stack
<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.

  • Both material states live on the element: the outset pair and the group-has-checked:shadow-[inset_…] twin sit adjacent in one class list, which makes the sign-flip discipline neumorphism depends on visually checkable.
  • The three-token theme (clay, lite, shade) becomes @theme entries, and the shadows read them through var() inside arbitrary values — so a retheme is still three lines, exactly as in the CSS version.
  • :checked ~ .plate .btn and its five siblings become group-has-checked: utilities, reaching the button, three icon bars, the word swap and four menu pills from one clipped checkbox.
  • The pressed state's colour change is now typed next to the shadow inversion, which is the point: shadow-only state signalling is the accessibility failure this style is known for.
  • The pills' pressed state uses active:shadow-[inset_…], keeping the inversion grammar identical across controls without a second rule.
  • The loud focus ring survives as focus-visible:outline-3 outline-offset-3 on every control — verbose, and that verbosity is what stops it being quietly dropped for aesthetics.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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: Neumorphic Tailwind Hamburger Toggle Button
Source: https://codefronts.com/navigation/tailwind-hamburger-menus/neumorphic-tailwind-hamburger-toggle-button/

Soft-UI physics done by the rules: the burger is a clay button raised by the canonical dual shadow, and pressing it does not move it &mdash; it inverts the shadows to inset, so the button sinks INTO the surface. In utilities both shadow states sit in one class list, which makes the light-source discipline that neumorphism lives by impossible to get wrong by accident.
## HTML
```html
<section class="thb-26 w-full min-h-svh bg-thb26-clay font-thb26-sans text-thb26-ink px-5 py-9" aria-label="Neumorphic hamburger toggle demo">
  <div class="group relative min-h-120 flex flex-col items-center gap-6.5 px-7 py-8.5 rounded-[22px] bg-thb26-clay border border-thb26-edge">
    <input type="checkbox" id="thb26-t" class="peer absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">
    <div class="flex flex-col items-center gap-3.5">
      <label for="thb26-t" class="grid place-items-center w-18.5 h-18.5 rounded-[22px] bg-thb26-clay cursor-pointer select-none shadow-[-7px_-7px_14px_var(--color-thb26-lite),7px_7px_14px_var(--color-thb26-shade)] transition-[box-shadow] duration-300 hover:shadow-[-9px_-9px_18px_var(--color-thb26-lite),9px_9px_18px_var(--color-thb26-shade)] peer-focus-visible:outline-3 peer-focus-visible:outline-thb26-acc peer-focus-visible:outline-offset-3 group-has-checked:shadow-[inset_-5px_-5px_10px_var(--color-thb26-lite),inset_5px_5px_10px_var(--color-thb26-shade)]">
        <span class="relative block w-6 h-4" aria-hidden="true">
          <i class="absolute left-0 top-0 w-6 h-[2.6px] rounded-[3px] bg-thb26-ink transition-[translate,rotate,background] duration-300 ease-thb26 group-has-checked:translate-y-[6.7px] group-has-checked:rotate-45 group-has-checked:bg-thb26-acc motion-reduce:transition-none"></i>
          <i class="absolute left-0 top-[6.7px] w-6 h-[2.6px] rounded-[3px] bg-thb26-ink transition-[opacity,scale,background] duration-250 ease-thb26 group-has-checked:opacity-0 group-has-checked:scale-x-40 group-has-checked:bg-thb26-acc motion-reduce:transition-none"></i>
          <i class="absolute left-0 top-[13.4px] w-6 h-[2.6px] rounded-[3px] bg-thb26-ink transition-[translate,rotate,background] duration-300 ease-thb26 group-has-checked:-translate-y-[6.7px] group-has-checked:-rotate-45 group-has-checked:bg-thb26-acc motion-reduce:transition-none"></i>
        </span>
      </label>
      <span class="relative block h-4 overflow-hidden font-thb26-mono text-[11px] tracking-[0.22em] uppercase text-thb26-mut" aria-hidden="true">
        <b class="block h-4 leading-4 font-bold transition-[translate] duration-350 ease-thb26 group-has-checked:-translate-y-4">rest</b>
        <b class="block h-4 leading-4 font-bold transition-[translate] duration-350 ease-thb26 group-has-checked:-translate-y-4">pressed</b>
      </span>
    </div>
    <nav aria-label="Menu" class="grid gap-3.5 w-[min(280px,100%)] p-1.5">
      <a href="#home" style="--i:0" class="group/p flex items-center gap-3.25 min-h-13 px-5 rounded-2xl bg-thb26-clay text-thb26-ink text-sm font-bold no-underline shadow-[-6px_-6px_12px_var(--color-thb26-lite),6px_6px_12px_var(--color-thb26-shade)] opacity-0 translate-y-3 pointer-events-none transition-[box-shadow,opacity,translate,color] duration-350 ease-thb26 hover:text-thb26-acc focus-visible:outline-3 focus-visible:outline-thb26-acc focus-visible:outline-offset-2 active:shadow-[inset_-4px_-4px_8px_var(--color-thb26-lite),inset_4px_4px_8px_var(--color-thb26-shade)] group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:pointer-events-auto group-has-checked:[transition-delay:calc(80ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">
        <i class="w-2 h-2 rounded-full bg-thb26-clay shadow-[inset_-2px_-2px_4px_var(--color-thb26-lite),inset_2px_2px_4px_var(--color-thb26-shade)] transition-[background,box-shadow] duration-200 group-hover/p:bg-thb26-acc group-hover/p:shadow-none group-focus-visible/p:bg-thb26-acc group-focus-visible/p:shadow-none" aria-hidden="true"></i>Home
      </a>
      <a href="#library" style="--i:1" class="group/p flex items-center gap-3.25 min-h-13 px-5 rounded-2xl bg-thb26-clay text-thb26-ink text-sm font-bold no-underline shadow-[-6px_-6px_12px_var(--color-thb26-lite),6px_6px_12px_var(--color-thb26-shade)] opacity-0 translate-y-3 pointer-events-none transition-[box-shadow,opacity,translate,color] duration-350 ease-thb26 hover:text-thb26-acc focus-visible:outline-3 focus-visible:outline-thb26-acc focus-visible:outline-offset-2 active:shadow-[inset_-4px_-4px_8px_var(--color-thb26-lite),inset_4px_4px_8px_var(--color-thb26-shade)] group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:pointer-events-auto group-has-checked:[transition-delay:calc(80ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">
        <i class="w-2 h-2 rounded-full bg-thb26-clay shadow-[inset_-2px_-2px_4px_var(--color-thb26-lite),inset_2px_2px_4px_var(--color-thb26-shade)] transition-[background,box-shadow] duration-200 group-hover/p:bg-thb26-acc group-hover/p:shadow-none group-focus-visible/p:bg-thb26-acc group-focus-visible/p:shadow-none" aria-hidden="true"></i>Library
      </a>
      <a href="#stats" style="--i:2" class="group/p flex items-center gap-3.25 min-h-13 px-5 rounded-2xl bg-thb26-clay text-thb26-ink text-sm font-bold no-underline shadow-[-6px_-6px_12px_var(--color-thb26-lite),6px_6px_12px_var(--color-thb26-shade)] opacity-0 translate-y-3 pointer-events-none transition-[box-shadow,opacity,translate,color] duration-350 ease-thb26 hover:text-thb26-acc focus-visible:outline-3 focus-visible:outline-thb26-acc focus-visible:outline-offset-2 active:shadow-[inset_-4px_-4px_8px_var(--color-thb26-lite),inset_4px_4px_8px_var(--color-thb26-shade)] group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:pointer-events-auto group-has-checked:[transition-delay:calc(80ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">
        <i class="w-2 h-2 rounded-full bg-thb26-clay shadow-[inset_-2px_-2px_4px_var(--color-thb26-lite),inset_2px_2px_4px_var(--color-thb26-shade)] transition-[background,box-shadow] duration-200 group-hover/p:bg-thb26-acc group-hover/p:shadow-none group-focus-visible/p:bg-thb26-acc group-focus-visible/p:shadow-none" aria-hidden="true"></i>Statistics
      </a>
      <a href="#settings" style="--i:3" class="group/p flex items-center gap-3.25 min-h-13 px-5 rounded-2xl bg-thb26-clay text-thb26-ink text-sm font-bold no-underline shadow-[-6px_-6px_12px_var(--color-thb26-lite),6px_6px_12px_var(--color-thb26-shade)] opacity-0 translate-y-3 pointer-events-none transition-[box-shadow,opacity,translate,color] duration-350 ease-thb26 hover:text-thb26-acc focus-visible:outline-3 focus-visible:outline-thb26-acc focus-visible:outline-offset-2 active:shadow-[inset_-4px_-4px_8px_var(--color-thb26-lite),inset_4px_4px_8px_var(--color-thb26-shade)] group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:pointer-events-auto group-has-checked:[transition-delay:calc(80ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">
        <i class="w-2 h-2 rounded-full bg-thb26-clay shadow-[inset_-2px_-2px_4px_var(--color-thb26-lite),inset_2px_2px_4px_var(--color-thb26-shade)] transition-[background,box-shadow] duration-200 group-hover/p:bg-thb26-acc group-hover/p:shadow-none group-focus-visible/p:bg-thb26-acc group-focus-visible/p:shadow-none" aria-hidden="true"></i>Settings
      </a>
    </nav>
    <p class="mt-auto max-w-[34ch] text-center text-xs leading-relaxed text-thb26-mut text-pretty" aria-hidden="true">same clay, two shadows &mdash; pressed = the pair goes <code class="font-thb26-mono text-[11px] px-1.5 py-0.5 rounded bg-thb26-code">inset</code>. Note the focus ring stays loud on purpose.</p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-thb26-clay: #e6e8ef;
  --color-thb26-clay: oklch(0.92 0.012 260);
  --color-thb26-lite: #fbfcff;
  --color-thb26-lite: oklch(0.99 0.005 260);
  --color-thb26-shade: #bcc0cd;
  --color-thb26-shade: oklch(0.78 0.02 260);
  --color-thb26-edge: #d5d8e2;
  --color-thb26-edge: oklch(0.87 0.014 260);
  --color-thb26-code: #d9dce6;
  --color-thb26-code: oklch(0.88 0.014 260);
  --color-thb26-ink: #454a5c;
  --color-thb26-ink: oklch(0.32 0.02 262);
  --color-thb26-mut: #6f7488;
  --color-thb26-mut: oklch(0.52 0.018 262);
  --color-thb26-acc: #3fa8b4;
  --color-thb26-acc: oklch(0.65 0.11 190);
  --font-thb26-sans: 'Space Grotesk', system-ui, sans-serif;
  --font-thb26-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-thb26: cubic-bezier(0.2, 0.7, 0.2, 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-26 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

One material rule, applied consistently. Every element is the SAME colour as the background, shaped purely by two opposing shadows from one imaginary light:

<label class="bg-thb26-clay
  shadow-[-7px_-7px_14px_var(--color-thb26-lite),7px_7px_14px_var(--color-thb26-shade)]
  group-has-checked:shadow-[inset_-5px_-5px_10px_var(--color-thb26-lite),inset_5px_5px_10px_var(--color-thb26-shade)]">

Raised is the outset pair; pressed is the SAME pair inset. That inversion is the entire interaction language — no translate, no scale; the light does the talking. Having both states adjacent in the class list is what makes the port worthwhile here: the #1 neumorphic bug is copy-pasted shadows with flipped signs, and side-by-side they are trivially checkable.

Radii and depth follow the material. Soft clay wants generous rounding, and the menu pills raise with a wider, softer shadow because farther from the surface means softer.

The accessibility warning, printed because this style is famous for failing audits. Neumorphism is intrinsically low-contrast: the button's edge against the surface is often under 1.4:1, far below the 3:1 WCAG 1.4.11 wants for control boundaries. This build compensates where it counts — ink-dark lines at 7.8:1, a real focus ring, and a pressed state that changes colour as well as shadow. Treat soft UI as a skin over compliant bones, not a substitute for them.

Make it yours

  • The whole theme is three tokens: --color-thb26-clay, -lite, -shade. Warm clay at hue 60, cool porcelain at 260 as shipped. Keep lightness within &plusmn;0.10 of the surface or the shadows go muddy.
  • Depth: 7px offset with 14px blur is standard, 4/8 is a subtle plaque, 10/24 is a pillow. Offset and blur scale together, roughly 1:2.
  • The menu pills flatten on press with the same inversion — restyle only the shadow recipe to retheme every control.
  • Add one saturated accent (the teal here) sparingly. Pure monochrome neumorphism reads as decoration rather than UI.
  • Stagger tempo for the pills is the 50ms multiplier in their [transition-delay:calc(…)].

Gotchas — read before shipping

  • Neumorphism only exists on midtone surfaces. On white the light shadow vanishes, on near-black the dark one does — if your brand background is either, this is the wrong pattern.
  • Keep the light source fixed forever. One element lit from elsewhere shatters the illusion, and with utilities that means checking the sign order every time you copy a shadow.
  • Soft shadows are paint-heavy. Fifty neumorphic elements measurably slow low-end scroll; this is a hero-element style, not a system.
  • Never remove the focus outline because it &ldquo;ruins the softness&rdquo;. That instinct is how soft-UI fails WCAG 2.4.7 — ring it anyway, offset outside the clay.
  • Do not signal state with shadow alone. The pressed state here also recolours the icon bars, because a shadow inversion is invisible to a low-vision user.
  • shadow-[…] with multiple layers must be typed closed-up with underscores; a space anywhere in the arbitrary value silently drops the whole utility.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Dual box-shadows and inset transitions run in every engine of the last decade; the floor quoted here is Tailwind v4's own generated sheet. This is also the easiest demo to port back to hex colours.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

Search CodeFronts

Loading…