10 / 36
All 36 in 36 Tailwind CSS Button Hover Effects
- 01Tailwind Magnetic Ripple Attraction Button Hover Effect
- 02Tailwind Cyberpunk Holographic Glitch Grid Button
- 03Tailwind Frosted Glassmorphism Shimmer Glow Button
- 04Tailwind Organic Blob Morph Button Hover Effect
- 05Tailwind Dual-Line Intersection Border Draw Button
- 06Tailwind Isometric Tactile 3D Depress Button
- 07Tailwind Infinite Sliding Kinetic Marquee Button
- 08Tailwind Chroma Conic Gradient Border Spin Button
- 09Tailwind Minimalist Border & Outline Drawing Hover Effects
- 10Tailwind Modern 3D Skeuomorphic Button Press Effects
- 11Tailwind Neo-Brutalist & Cyberpunk Button Glow Effects
- 12Tailwind Sliding Background Color Wipe Button Effects
- 13Tailwind Liquid Fill & Organic Morphing Button Effects
- 14Tailwind Kinetic Text Flip & Icon Reveal Button Effects
- 15Tailwind Liquid Wave Fill Button Hover Effect
- 16Tailwind Magnetic Border Drift Button
- 17Tailwind Glitch Slice Button Hover Effect
- 18Tailwind Neon Pulse Glow Button Hover Effect
- 19Tailwind Split Reveal Curtain Button Effect
- 20Tailwind Diagonal Shutter Sweep Button Effect
- 21Tailwind Elastic Lift Spring Button Hover Effect
- 22Tailwind Center Underline Draw Link Button Effects
- 23Tailwind 3D Flip Card Button Hover Effect
- 24Tailwind Particle Burst Confetti Button Effect
- 25Tailwind Slide Doors Closing Panel Button Effect
- 26Tailwind Typewriter Retype Terminal Button Effect
- 27Tailwind Hard Shadow Shift Flat Button Effect
- 28Tailwind Material Ripple Wave Button Effect
- 29Tailwind Rainbow Gradient Border Spin Button
- 30Tailwind Morph Shape Silhouette Button Effects
- 31Tailwind Icon Slide In Directional Button Effect
- 32Tailwind Cursor Spotlight Glow Button
- 33Tailwind Stagger Fill Equaliser Button Effect
- 34Tailwind Sunburst Expand Radiant Button Effect
- 35Tailwind 3D Press Plunger Key Button Effect
- 36Tailwind Add-to-Cart Liquid Glass Ripple Button
Tailwind Modern 3D Skeuomorphic Button Press Effects
A soft tactile neumorphic CTA that appears extruded from the surface using paired light/dark shadows, depressing inward on press as the shadows invert. The port's contribution is legibility: four shadow stacks that used to live in four rules now sit next to each other, where you can actually see that they invert.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tbh-10-group w-full min-h-svh flex flex-wrap">
<section class="tbh-10a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh10-sans bg-tbh10a-surf" aria-label="Skeuomorphic press button">
<button type="button" class="cursor-pointer border-0 rounded-2xl px-10 py-[18px] text-base font-extrabold text-tbh10a-ink bg-tbh10a-surf shadow-[-6px_-6px_14px_color-mix(in_oklab,var(--color-tbh10a-surf)_60%,white),7px_7px_16px_color-mix(in_oklab,var(--color-tbh10a-surf)_62%,black)] transition-[translate,box-shadow,color] duration-[180ms] hover:text-tbh10a-acc hover:shadow-[-8px_-8px_18px_color-mix(in_oklab,var(--color-tbh10a-surf)_55%,white),9px_9px_20px_color-mix(in_oklab,var(--color-tbh10a-surf)_58%,black)] focus-visible:text-tbh10a-acc active:translate-y-0.5 active:shadow-[inset_-4px_-4px_10px_color-mix(in_oklab,var(--color-tbh10a-surf)_60%,white),inset_5px_5px_12px_color-mix(in_oklab,var(--color-tbh10a-surf)_62%,black)] focus-visible:outline-3 focus-visible:outline-tbh10a-acc focus-visible:outline-offset-4 motion-reduce:transition-[box-shadow,color] motion-reduce:duration-[120ms]">
Save changes
</button>
</section>
<section class="tbh-10b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh10-sans bg-tbh10b-stage" aria-label="Glossy plastic button">
<button type="button" class="relative overflow-hidden cursor-pointer border-0 rounded-[14px] px-[42px] py-[18px] text-base font-extrabold text-white bg-[linear-gradient(180deg,color-mix(in_oklab,var(--color-tbh10b-acc)_78%,white),var(--color-tbh10b-acc))] [text-shadow:0_1px_1px_oklch(0_0_0/0.25)] shadow-[inset_0_1px_0_oklch(1_0_0/0.7),inset_0_-6px_12px_-6px_oklch(0_0_0/0.4),0_10px_20px_-8px_color-mix(in_oklab,var(--color-tbh10b-acc)_60%,transparent)] transition-[translate,box-shadow] duration-[140ms] before:content-[''] before:absolute before:top-0.5 before:left-2 before:right-2 before:h-[42%] before:pointer-events-none before:[border-radius:12px_12px_40%_40%/12px_12px_60%_60%] before:bg-[linear-gradient(180deg,oklch(1_0_0/0.55),transparent)] hover:-translate-y-px focus-visible:-translate-y-px active:translate-y-0.5 active:shadow-[inset_0_1px_0_oklch(1_0_0/0.5),inset_0_4px_10px_-4px_oklch(0_0_0/0.5),0_4px_10px_-6px_color-mix(in_oklab,var(--color-tbh10b-acc)_60%,transparent)] focus-visible:outline-3 focus-visible:outline-tbh10b-acc focus-visible:outline-offset-4 motion-reduce:transition-[box-shadow] motion-reduce:duration-100 motion-reduce:hover:translate-y-0">
Confirm order
</button>
</section>
</div><div class="tbh-10-group w-full min-h-svh flex flex-wrap">
<section class="tbh-10a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh10-sans bg-tbh10a-surf" aria-label="Skeuomorphic press button">
<button type="button" class="cursor-pointer border-0 rounded-2xl px-10 py-[18px] text-base font-extrabold text-tbh10a-ink bg-tbh10a-surf shadow-[-6px_-6px_14px_color-mix(in_oklab,var(--color-tbh10a-surf)_60%,white),7px_7px_16px_color-mix(in_oklab,var(--color-tbh10a-surf)_62%,black)] transition-[translate,box-shadow,color] duration-[180ms] hover:text-tbh10a-acc hover:shadow-[-8px_-8px_18px_color-mix(in_oklab,var(--color-tbh10a-surf)_55%,white),9px_9px_20px_color-mix(in_oklab,var(--color-tbh10a-surf)_58%,black)] focus-visible:text-tbh10a-acc active:translate-y-0.5 active:shadow-[inset_-4px_-4px_10px_color-mix(in_oklab,var(--color-tbh10a-surf)_60%,white),inset_5px_5px_12px_color-mix(in_oklab,var(--color-tbh10a-surf)_62%,black)] focus-visible:outline-3 focus-visible:outline-tbh10a-acc focus-visible:outline-offset-4 motion-reduce:transition-[box-shadow,color] motion-reduce:duration-[120ms]">
Save changes
</button>
</section>
<section class="tbh-10b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh10-sans bg-tbh10b-stage" aria-label="Glossy plastic button">
<button type="button" class="relative overflow-hidden cursor-pointer border-0 rounded-[14px] px-[42px] py-[18px] text-base font-extrabold text-white bg-[linear-gradient(180deg,color-mix(in_oklab,var(--color-tbh10b-acc)_78%,white),var(--color-tbh10b-acc))] [text-shadow:0_1px_1px_oklch(0_0_0/0.25)] shadow-[inset_0_1px_0_oklch(1_0_0/0.7),inset_0_-6px_12px_-6px_oklch(0_0_0/0.4),0_10px_20px_-8px_color-mix(in_oklab,var(--color-tbh10b-acc)_60%,transparent)] transition-[translate,box-shadow] duration-[140ms] before:content-[''] before:absolute before:top-0.5 before:left-2 before:right-2 before:h-[42%] before:pointer-events-none before:[border-radius:12px_12px_40%_40%/12px_12px_60%_60%] before:bg-[linear-gradient(180deg,oklch(1_0_0/0.55),transparent)] hover:-translate-y-px focus-visible:-translate-y-px active:translate-y-0.5 active:shadow-[inset_0_1px_0_oklch(1_0_0/0.5),inset_0_4px_10px_-4px_oklch(0_0_0/0.5),0_4px_10px_-6px_color-mix(in_oklab,var(--color-tbh10b-acc)_60%,transparent)] focus-visible:outline-3 focus-visible:outline-tbh10b-acc focus-visible:outline-offset-4 motion-reduce:transition-[box-shadow] motion-reduce:duration-100 motion-reduce:hover:translate-y-0">
Confirm order
</button>
</section>
</div>@import "tailwindcss";
@theme {
--color-tbh10a-surf: #e6e9ef;
--color-tbh10a-surf: oklch(0.92 0.008 265);
--color-tbh10a-ink: #6b7280;
--color-tbh10a-ink: oklch(0.5 0.02 265);
--color-tbh10a-acc: #5b7cdb;
--color-tbh10a-acc: oklch(0.55 0.16 265);
--color-tbh10b-acc: #4d7ce0;
--color-tbh10b-acc: oklch(0.6 0.18 250);
--color-tbh10b-stage: #e8ecf3;
--color-tbh10b-stage: oklch(0.93 0.01 255);
--font-tbh10-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: every shadow stack, gradient and text-shadow is an arbitrary value. */
.tbh-10-group {
width: 100%;
min-height: 100svh;
}
.tbh-10a,
.tbh-10b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh10a-surf: #e6e9ef;
--color-tbh10a-surf: oklch(0.92 0.008 265);
--color-tbh10a-ink: #6b7280;
--color-tbh10a-ink: oklch(0.5 0.02 265);
--color-tbh10a-acc: #5b7cdb;
--color-tbh10a-acc: oklch(0.55 0.16 265);
--color-tbh10b-acc: #4d7ce0;
--color-tbh10b-acc: oklch(0.6 0.18 250);
--color-tbh10b-stage: #e8ecf3;
--color-tbh10b-stage: oklch(0.93 0.01 255);
--font-tbh10-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: every shadow stack, gradient and text-shadow is an arbitrary value. */
.tbh-10-group {
width: 100%;
min-height: 100svh;
}
.tbh-10a,
.tbh-10b {
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-tbh10a-surf: #e6e9ef;
--color-tbh10a-surf: oklch(0.92 0.008 265);
--color-tbh10a-ink: #6b7280;
--color-tbh10a-ink: oklch(0.5 0.02 265);
--color-tbh10a-acc: #5b7cdb;
--color-tbh10a-acc: oklch(0.55 0.16 265);
--color-tbh10b-acc: #4d7ce0;
--color-tbh10b-acc: oklch(0.6 0.18 250);
--color-tbh10b-stage: #e8ecf3;
--color-tbh10b-stage: oklch(0.93 0.01 255);
--font-tbh10-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: every shadow stack, gradient and text-shadow is an arbitrary value. */
.tbh-10-group {
width: 100%;
min-height: 100svh;
}
.tbh-10a,
.tbh-10b {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh10a-surf: #e6e9ef;
--color-tbh10a-surf: oklch(0.92 0.008 265);
--color-tbh10a-ink: #6b7280;
--color-tbh10a-ink: oklch(0.5 0.02 265);
--color-tbh10a-acc: #5b7cdb;
--color-tbh10a-acc: oklch(0.55 0.16 265);
--color-tbh10b-acc: #4d7ce0;
--color-tbh10b-acc: oklch(0.6 0.18 250);
--color-tbh10b-stage: #e8ecf3;
--color-tbh10b-stage: oklch(0.93 0.01 255);
--font-tbh10-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: every shadow stack, gradient and text-shadow is an arbitrary value. */
.tbh-10-group {
width: 100%;
min-height: 100svh;
}
.tbh-10a,
.tbh-10b {
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&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.
- Four shadow stacks in four rules become four stacks in one class list, and the inversion is finally visible as an inversion — this is the demo where the port's benefit is purely about reading the code, since not one value changed.
color-mix()inside an arbitrary shadow value works exactly as it does in CSS, so the derived-from-one-token architecture survives the port intact. Deriving the shadows is what keeps neumorphism's shared-colour rule from being a comment nobody reads.- UI change from the CSS original: both designs now mirror the hover state on
focus-visible:(design A's accent label, design B's 1px lift), which on a borderless surface is the difference between a visible and an invisible focus.
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 Modern 3D Skeuomorphic Button Press Effects
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-modern-3d-skeuomorphic-button-press-effects/
A soft tactile neumorphic CTA that appears extruded from the surface using paired light/dark shadows, depressing inward on press as the shadows invert. The port's contribution is legibility: four shadow stacks that used to live in four rules now sit next to each other, where you can actually see that they invert.
## HTML
```html
<div class="tbh-10-group w-full min-h-svh flex flex-wrap">
<section class="tbh-10a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh10-sans bg-tbh10a-surf" aria-label="Skeuomorphic press button">
<button type="button" class="cursor-pointer border-0 rounded-2xl px-10 py-[18px] text-base font-extrabold text-tbh10a-ink bg-tbh10a-surf shadow-[-6px_-6px_14px_color-mix(in_oklab,var(--color-tbh10a-surf)_60%,white),7px_7px_16px_color-mix(in_oklab,var(--color-tbh10a-surf)_62%,black)] transition-[translate,box-shadow,color] duration-[180ms] hover:text-tbh10a-acc hover:shadow-[-8px_-8px_18px_color-mix(in_oklab,var(--color-tbh10a-surf)_55%,white),9px_9px_20px_color-mix(in_oklab,var(--color-tbh10a-surf)_58%,black)] focus-visible:text-tbh10a-acc active:translate-y-0.5 active:shadow-[inset_-4px_-4px_10px_color-mix(in_oklab,var(--color-tbh10a-surf)_60%,white),inset_5px_5px_12px_color-mix(in_oklab,var(--color-tbh10a-surf)_62%,black)] focus-visible:outline-3 focus-visible:outline-tbh10a-acc focus-visible:outline-offset-4 motion-reduce:transition-[box-shadow,color] motion-reduce:duration-[120ms]">
Save changes
</button>
</section>
<section class="tbh-10b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh10-sans bg-tbh10b-stage" aria-label="Glossy plastic button">
<button type="button" class="relative overflow-hidden cursor-pointer border-0 rounded-[14px] px-[42px] py-[18px] text-base font-extrabold text-white bg-[linear-gradient(180deg,color-mix(in_oklab,var(--color-tbh10b-acc)_78%,white),var(--color-tbh10b-acc))] [text-shadow:0_1px_1px_oklch(0_0_0/0.25)] shadow-[inset_0_1px_0_oklch(1_0_0/0.7),inset_0_-6px_12px_-6px_oklch(0_0_0/0.4),0_10px_20px_-8px_color-mix(in_oklab,var(--color-tbh10b-acc)_60%,transparent)] transition-[translate,box-shadow] duration-[140ms] before:content-[''] before:absolute before:top-0.5 before:left-2 before:right-2 before:h-[42%] before:pointer-events-none before:[border-radius:12px_12px_40%_40%/12px_12px_60%_60%] before:bg-[linear-gradient(180deg,oklch(1_0_0/0.55),transparent)] hover:-translate-y-px focus-visible:-translate-y-px active:translate-y-0.5 active:shadow-[inset_0_1px_0_oklch(1_0_0/0.5),inset_0_4px_10px_-4px_oklch(0_0_0/0.5),0_4px_10px_-6px_color-mix(in_oklab,var(--color-tbh10b-acc)_60%,transparent)] focus-visible:outline-3 focus-visible:outline-tbh10b-acc focus-visible:outline-offset-4 motion-reduce:transition-[box-shadow] motion-reduce:duration-100 motion-reduce:hover:translate-y-0">
Confirm order
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh10a-surf: #e6e9ef;
--color-tbh10a-surf: oklch(0.92 0.008 265);
--color-tbh10a-ink: #6b7280;
--color-tbh10a-ink: oklch(0.5 0.02 265);
--color-tbh10a-acc: #5b7cdb;
--color-tbh10a-acc: oklch(0.55 0.16 265);
--color-tbh10b-acc: #4d7ce0;
--color-tbh10b-acc: oklch(0.6 0.18 250);
--color-tbh10b-stage: #e8ecf3;
--color-tbh10b-stage: oklch(0.93 0.01 255);
--font-tbh10-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: every shadow stack, gradient and text-shadow is an arbitrary value. */
.tbh-10-group {
width: 100%;
min-height: 100svh;
}
.tbh-10a,
.tbh-10b {
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: Tailwind Modern 3D Skeuomorphic Button Press Effects
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-modern-3d-skeuomorphic-button-press-effects/
A soft tactile neumorphic CTA that appears extruded from the surface using paired light/dark shadows, depressing inward on press as the shadows invert. The port's contribution is legibility: four shadow stacks that used to live in four rules now sit next to each other, where you can actually see that they invert.
## HTML
```html
<div class="tbh-10-group w-full min-h-svh flex flex-wrap">
<section class="tbh-10a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh10-sans bg-tbh10a-surf" aria-label="Skeuomorphic press button">
<button type="button" class="cursor-pointer border-0 rounded-2xl px-10 py-[18px] text-base font-extrabold text-tbh10a-ink bg-tbh10a-surf shadow-[-6px_-6px_14px_color-mix(in_oklab,var(--color-tbh10a-surf)_60%,white),7px_7px_16px_color-mix(in_oklab,var(--color-tbh10a-surf)_62%,black)] transition-[translate,box-shadow,color] duration-[180ms] hover:text-tbh10a-acc hover:shadow-[-8px_-8px_18px_color-mix(in_oklab,var(--color-tbh10a-surf)_55%,white),9px_9px_20px_color-mix(in_oklab,var(--color-tbh10a-surf)_58%,black)] focus-visible:text-tbh10a-acc active:translate-y-0.5 active:shadow-[inset_-4px_-4px_10px_color-mix(in_oklab,var(--color-tbh10a-surf)_60%,white),inset_5px_5px_12px_color-mix(in_oklab,var(--color-tbh10a-surf)_62%,black)] focus-visible:outline-3 focus-visible:outline-tbh10a-acc focus-visible:outline-offset-4 motion-reduce:transition-[box-shadow,color] motion-reduce:duration-[120ms]">
Save changes
</button>
</section>
<section class="tbh-10b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh10-sans bg-tbh10b-stage" aria-label="Glossy plastic button">
<button type="button" class="relative overflow-hidden cursor-pointer border-0 rounded-[14px] px-[42px] py-[18px] text-base font-extrabold text-white bg-[linear-gradient(180deg,color-mix(in_oklab,var(--color-tbh10b-acc)_78%,white),var(--color-tbh10b-acc))] [text-shadow:0_1px_1px_oklch(0_0_0/0.25)] shadow-[inset_0_1px_0_oklch(1_0_0/0.7),inset_0_-6px_12px_-6px_oklch(0_0_0/0.4),0_10px_20px_-8px_color-mix(in_oklab,var(--color-tbh10b-acc)_60%,transparent)] transition-[translate,box-shadow] duration-[140ms] before:content-[''] before:absolute before:top-0.5 before:left-2 before:right-2 before:h-[42%] before:pointer-events-none before:[border-radius:12px_12px_40%_40%/12px_12px_60%_60%] before:bg-[linear-gradient(180deg,oklch(1_0_0/0.55),transparent)] hover:-translate-y-px focus-visible:-translate-y-px active:translate-y-0.5 active:shadow-[inset_0_1px_0_oklch(1_0_0/0.5),inset_0_4px_10px_-4px_oklch(0_0_0/0.5),0_4px_10px_-6px_color-mix(in_oklab,var(--color-tbh10b-acc)_60%,transparent)] focus-visible:outline-3 focus-visible:outline-tbh10b-acc focus-visible:outline-offset-4 motion-reduce:transition-[box-shadow] motion-reduce:duration-100 motion-reduce:hover:translate-y-0">
Confirm order
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh10a-surf: #e6e9ef;
--color-tbh10a-surf: oklch(0.92 0.008 265);
--color-tbh10a-ink: #6b7280;
--color-tbh10a-ink: oklch(0.5 0.02 265);
--color-tbh10a-acc: #5b7cdb;
--color-tbh10a-acc: oklch(0.55 0.16 265);
--color-tbh10b-acc: #4d7ce0;
--color-tbh10b-acc: oklch(0.6 0.18 250);
--color-tbh10b-stage: #e8ecf3;
--color-tbh10b-stage: oklch(0.93 0.01 255);
--font-tbh10-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: every shadow stack, gradient and text-shadow is an arbitrary value. */
.tbh-10-group {
width: 100%;
min-height: 100svh;
}
.tbh-10a,
.tbh-10b {
min-height: 100svh;
}
```How this works
Neumorphism is two shadows and a shared colour. The button and its stage must be the same base colour — the shape exists only as light. Deriving both shadows from one @theme token with color-mix() means that constraint is enforced by the code rather than by discipline.
The press inverts, it does not move much. active:translate-y-0.5 plus inset shadows: 2px of travel and a complete lighting flip. The flip is what sells it, which is why the resting and active stacks are worth reading side by side.
Design B is the glossy-plastic cousin. A vertical gradient fill, an inset rim highlight, a text-shadow for moulded type, and a before: gloss cap with an eight-value radius. Same physics, different decade.
Make it yours
- Surface:
--color-tbh10a-surf. Both shadows and the stage read it, so one change rethemes the demo. - Softness: the blur radii (
14px/16px). Larger is softer and flatter; smaller is crisper and more plastic. - Press depth:
active:translate-y-0.5and the inset offsets — they should change together. - Accent: design A shifts the label to
hover:text-tbh10a-acc, the only colour in an otherwise monochrome surface.
Gotchas — read before shipping
- Button and stage MUST share the base colour. A different background breaks the illusion instantly, which is the whole reason the surface is a token.
- Neumorphism has no visible border, so a focus ring is non-negotiable: both designs keep
focus-visible:outline-3 ... outline-offset-4in an accent colour. - Label contrast is the standing risk on a low-contrast surface — design A's ink is
oklch(0.5 ...)on a0.92surface, which clears 4.5:1. Lighten either and re-check. - Design B's gloss cap needs
pointer-events-none; it covers the top 42% of the button and would otherwise eat clicks.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Layered and inset box-shadow are universal; color-mix() is Tailwind v4's own floor, so no additional gate.