30 / 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 Morph Shape Silhouette Button Effects
Three silhouette morphs: sharp corners rounding into a pill, a pill snapping to an asymmetric notch, and a circular icon button expanding into a labelled bar. The third is the one worth copying — a floating action button that earns its label on hover.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tbh-30-group w-full min-h-svh flex flex-wrap">
<section class="tbh-30a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30a-stage" aria-label="Morph shape button">
<button type="button" class="cursor-pointer border-0 rounded px-[38px] py-4 text-base font-extrabold text-white bg-tbh30a-acc shadow-[0_10px_22px_-12px_color-mix(in_oklab,var(--color-tbh30a-acc)_70%,transparent)] transition-[border-radius,translate,scale,background-color] duration-500 ease-[cubic-bezier(.4,0,.2,1)] hover:rounded-[30px] hover:-translate-y-0.5 hover:bg-[color-mix(in_oklab,var(--color-tbh30a-acc)_88%,black)] focus-visible:rounded-[30px] focus-visible:-translate-y-0.5 active:translate-y-0 active:scale-[.98] focus-visible:outline-3 focus-visible:outline-tbh30a-acc focus-visible:outline-offset-4 motion-reduce:transition-[border-radius,background-color] motion-reduce:duration-[250ms] motion-reduce:hover:translate-y-0">
Send message
</button>
</section>
<section class="tbh-30b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30b-stage" aria-label="Pill to notched button">
<button type="button" class="cursor-pointer border-0 rounded-[30px] px-10 py-4 text-base font-extrabold text-white bg-tbh30b-acc shadow-[0_12px_24px_-12px_var(--color-tbh30b-acc)] transition-[border-radius,translate,scale] duration-500 ease-[cubic-bezier(.4,0,.2,1)] hover:[border-radius:16px_4px_16px_4px] hover:-translate-y-0.5 focus-visible:[border-radius:16px_4px_16px_4px] focus-visible:-translate-y-0.5 active:translate-y-0 active:scale-[.98] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-[border-radius] motion-reduce:duration-[250ms] motion-reduce:hover:translate-y-0">
Reserve seat
</button>
</section>
<section class="tbh-30c flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30c-stage" aria-label="Circle to bar button">
<button type="button" class="group/btn relative grid place-items-center overflow-hidden h-14 w-14 cursor-pointer border-0 rounded-[28px] text-base font-extrabold text-white bg-tbh30c-acc shadow-[0_12px_24px_-12px_var(--color-tbh30c-acc)] transition-[width,border-radius] duration-[450ms] ease-[cubic-bezier(.4,0,.2,1)] hover:w-[170px] hover:rounded-[14px] focus-visible:w-[170px] focus-visible:rounded-[14px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-none">
<span aria-hidden="true" class="text-2xl leading-none transition-opacity duration-200 group-hover/btn:opacity-0 group-hover/btn:duration-150 group-focus-visible/btn:opacity-0">+</span>
<span class="absolute whitespace-nowrap opacity-0 transition-opacity duration-200 group-hover/btn:opacity-100 group-hover/btn:delay-[250ms] group-focus-visible/btn:opacity-100 group-focus-visible/btn:delay-[250ms]">Add item</span>
</button>
</section>
</div><div class="tbh-30-group w-full min-h-svh flex flex-wrap">
<section class="tbh-30a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30a-stage" aria-label="Morph shape button">
<button type="button" class="cursor-pointer border-0 rounded px-[38px] py-4 text-base font-extrabold text-white bg-tbh30a-acc shadow-[0_10px_22px_-12px_color-mix(in_oklab,var(--color-tbh30a-acc)_70%,transparent)] transition-[border-radius,translate,scale,background-color] duration-500 ease-[cubic-bezier(.4,0,.2,1)] hover:rounded-[30px] hover:-translate-y-0.5 hover:bg-[color-mix(in_oklab,var(--color-tbh30a-acc)_88%,black)] focus-visible:rounded-[30px] focus-visible:-translate-y-0.5 active:translate-y-0 active:scale-[.98] focus-visible:outline-3 focus-visible:outline-tbh30a-acc focus-visible:outline-offset-4 motion-reduce:transition-[border-radius,background-color] motion-reduce:duration-[250ms] motion-reduce:hover:translate-y-0">
Send message
</button>
</section>
<section class="tbh-30b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30b-stage" aria-label="Pill to notched button">
<button type="button" class="cursor-pointer border-0 rounded-[30px] px-10 py-4 text-base font-extrabold text-white bg-tbh30b-acc shadow-[0_12px_24px_-12px_var(--color-tbh30b-acc)] transition-[border-radius,translate,scale] duration-500 ease-[cubic-bezier(.4,0,.2,1)] hover:[border-radius:16px_4px_16px_4px] hover:-translate-y-0.5 focus-visible:[border-radius:16px_4px_16px_4px] focus-visible:-translate-y-0.5 active:translate-y-0 active:scale-[.98] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-[border-radius] motion-reduce:duration-[250ms] motion-reduce:hover:translate-y-0">
Reserve seat
</button>
</section>
<section class="tbh-30c flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30c-stage" aria-label="Circle to bar button">
<button type="button" class="group/btn relative grid place-items-center overflow-hidden h-14 w-14 cursor-pointer border-0 rounded-[28px] text-base font-extrabold text-white bg-tbh30c-acc shadow-[0_12px_24px_-12px_var(--color-tbh30c-acc)] transition-[width,border-radius] duration-[450ms] ease-[cubic-bezier(.4,0,.2,1)] hover:w-[170px] hover:rounded-[14px] focus-visible:w-[170px] focus-visible:rounded-[14px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-none">
<span aria-hidden="true" class="text-2xl leading-none transition-opacity duration-200 group-hover/btn:opacity-0 group-hover/btn:duration-150 group-focus-visible/btn:opacity-0">+</span>
<span class="absolute whitespace-nowrap opacity-0 transition-opacity duration-200 group-hover/btn:opacity-100 group-hover/btn:delay-[250ms] group-focus-visible/btn:opacity-100 group-focus-visible/btn:delay-[250ms]">Add item</span>
</button>
</section>
</div>@import "tailwindcss";
@theme {
--color-tbh30a-acc: #3d8fb8;
--color-tbh30a-acc: oklch(0.58 0.17 210);
--color-tbh30a-stage: #f4f6f8;
--color-tbh30a-stage: oklch(0.97 0.005 220);
--color-tbh30b-acc: #d4664a;
--color-tbh30b-acc: oklch(0.6 0.16 25);
--color-tbh30b-stage: #1a1010;
--color-tbh30b-stage: oklch(0.18 0.02 25);
--color-tbh30c-acc: #4aab72;
--color-tbh30c-acc: oklch(0.62 0.16 150);
--color-tbh30c-stage: #0c160f;
--color-tbh30c-stage: oklch(0.15 0.02 150);
--font-tbh30-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: three radius targets and one width expansion. */
.tbh-30-group {
width: 100%;
min-height: 100svh;
}
.tbh-30a,
.tbh-30b,
.tbh-30c {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh30a-acc: #3d8fb8;
--color-tbh30a-acc: oklch(0.58 0.17 210);
--color-tbh30a-stage: #f4f6f8;
--color-tbh30a-stage: oklch(0.97 0.005 220);
--color-tbh30b-acc: #d4664a;
--color-tbh30b-acc: oklch(0.6 0.16 25);
--color-tbh30b-stage: #1a1010;
--color-tbh30b-stage: oklch(0.18 0.02 25);
--color-tbh30c-acc: #4aab72;
--color-tbh30c-acc: oklch(0.62 0.16 150);
--color-tbh30c-stage: #0c160f;
--color-tbh30c-stage: oklch(0.15 0.02 150);
--font-tbh30-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: three radius targets and one width expansion. */
.tbh-30-group {
width: 100%;
min-height: 100svh;
}
.tbh-30a,
.tbh-30b,
.tbh-30c {
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-tbh30a-acc: #3d8fb8;
--color-tbh30a-acc: oklch(0.58 0.17 210);
--color-tbh30a-stage: #f4f6f8;
--color-tbh30a-stage: oklch(0.97 0.005 220);
--color-tbh30b-acc: #d4664a;
--color-tbh30b-acc: oklch(0.6 0.16 25);
--color-tbh30b-stage: #1a1010;
--color-tbh30b-stage: oklch(0.18 0.02 25);
--color-tbh30c-acc: #4aab72;
--color-tbh30c-acc: oklch(0.62 0.16 150);
--color-tbh30c-stage: #0c160f;
--color-tbh30c-stage: oklch(0.15 0.02 150);
--font-tbh30-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: three radius targets and one width expansion. */
.tbh-30-group {
width: 100%;
min-height: 100svh;
}
.tbh-30a,
.tbh-30b,
.tbh-30c {
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tbh30a-acc: #3d8fb8;
--color-tbh30a-acc: oklch(0.58 0.17 210);
--color-tbh30a-stage: #f4f6f8;
--color-tbh30a-stage: oklch(0.97 0.005 220);
--color-tbh30b-acc: #d4664a;
--color-tbh30b-acc: oklch(0.6 0.16 25);
--color-tbh30b-stage: #1a1010;
--color-tbh30b-stage: oklch(0.18 0.02 25);
--color-tbh30c-acc: #4aab72;
--color-tbh30c-acc: oklch(0.62 0.16 150);
--color-tbh30c-stage: #0c160f;
--color-tbh30c-stage: oklch(0.15 0.02 150);
--font-tbh30-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: three radius targets and one width expansion. */
.tbh-30-group {
width: 100%;
min-height: 100svh;
}
.tbh-30a,
.tbh-30b,
.tbh-30c {
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.
- Design C is the port's best showing in this entry: the label sequencing (
group-hover/btn:duration-150out,group-hover/btn:delay-[250ms]in) is stated on the two labels themselves, where in CSS it was twotransition-duration/transition-delayoverrides in separate hover rules and very easy to desynchronise. - Design B's asymmetric target has to be an arbitrary
[border-radius:16px_4px_16px_4px]— Tailwind's per-corner utilities could express it in four classes, but the single value keeps the shape readable as one decision. - UI change from the CSS original: all three morphs fire on
focus-visible:, and designs A and B gainedactive:states that compose a scale with the lift rather than replacing the transform.
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 Morph Shape Silhouette Button Effects
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-morph-shape-silhouette-button-effects/
Three silhouette morphs: sharp corners rounding into a pill, a pill snapping to an asymmetric notch, and a circular icon button expanding into a labelled bar. The third is the one worth copying — a floating action button that earns its label on hover.
## HTML
```html
<div class="tbh-30-group w-full min-h-svh flex flex-wrap">
<section class="tbh-30a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30a-stage" aria-label="Morph shape button">
<button type="button" class="cursor-pointer border-0 rounded px-[38px] py-4 text-base font-extrabold text-white bg-tbh30a-acc shadow-[0_10px_22px_-12px_color-mix(in_oklab,var(--color-tbh30a-acc)_70%,transparent)] transition-[border-radius,translate,scale,background-color] duration-500 ease-[cubic-bezier(.4,0,.2,1)] hover:rounded-[30px] hover:-translate-y-0.5 hover:bg-[color-mix(in_oklab,var(--color-tbh30a-acc)_88%,black)] focus-visible:rounded-[30px] focus-visible:-translate-y-0.5 active:translate-y-0 active:scale-[.98] focus-visible:outline-3 focus-visible:outline-tbh30a-acc focus-visible:outline-offset-4 motion-reduce:transition-[border-radius,background-color] motion-reduce:duration-[250ms] motion-reduce:hover:translate-y-0">
Send message
</button>
</section>
<section class="tbh-30b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30b-stage" aria-label="Pill to notched button">
<button type="button" class="cursor-pointer border-0 rounded-[30px] px-10 py-4 text-base font-extrabold text-white bg-tbh30b-acc shadow-[0_12px_24px_-12px_var(--color-tbh30b-acc)] transition-[border-radius,translate,scale] duration-500 ease-[cubic-bezier(.4,0,.2,1)] hover:[border-radius:16px_4px_16px_4px] hover:-translate-y-0.5 focus-visible:[border-radius:16px_4px_16px_4px] focus-visible:-translate-y-0.5 active:translate-y-0 active:scale-[.98] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-[border-radius] motion-reduce:duration-[250ms] motion-reduce:hover:translate-y-0">
Reserve seat
</button>
</section>
<section class="tbh-30c flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30c-stage" aria-label="Circle to bar button">
<button type="button" class="group/btn relative grid place-items-center overflow-hidden h-14 w-14 cursor-pointer border-0 rounded-[28px] text-base font-extrabold text-white bg-tbh30c-acc shadow-[0_12px_24px_-12px_var(--color-tbh30c-acc)] transition-[width,border-radius] duration-[450ms] ease-[cubic-bezier(.4,0,.2,1)] hover:w-[170px] hover:rounded-[14px] focus-visible:w-[170px] focus-visible:rounded-[14px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-none">
<span aria-hidden="true" class="text-2xl leading-none transition-opacity duration-200 group-hover/btn:opacity-0 group-hover/btn:duration-150 group-focus-visible/btn:opacity-0">+</span>
<span class="absolute whitespace-nowrap opacity-0 transition-opacity duration-200 group-hover/btn:opacity-100 group-hover/btn:delay-[250ms] group-focus-visible/btn:opacity-100 group-focus-visible/btn:delay-[250ms]">Add item</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh30a-acc: #3d8fb8;
--color-tbh30a-acc: oklch(0.58 0.17 210);
--color-tbh30a-stage: #f4f6f8;
--color-tbh30a-stage: oklch(0.97 0.005 220);
--color-tbh30b-acc: #d4664a;
--color-tbh30b-acc: oklch(0.6 0.16 25);
--color-tbh30b-stage: #1a1010;
--color-tbh30b-stage: oklch(0.18 0.02 25);
--color-tbh30c-acc: #4aab72;
--color-tbh30c-acc: oklch(0.62 0.16 150);
--color-tbh30c-stage: #0c160f;
--color-tbh30c-stage: oklch(0.15 0.02 150);
--font-tbh30-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: three radius targets and one width expansion. */
.tbh-30-group {
width: 100%;
min-height: 100svh;
}
.tbh-30a,
.tbh-30b,
.tbh-30c {
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 Morph Shape Silhouette Button Effects
Source: https://codefronts.com/motion/tailwind-css-button-hover-effects/tailwind-morph-shape-silhouette-button-effects/
Three silhouette morphs: sharp corners rounding into a pill, a pill snapping to an asymmetric notch, and a circular icon button expanding into a labelled bar. The third is the one worth copying — a floating action button that earns its label on hover.
## HTML
```html
<div class="tbh-30-group w-full min-h-svh flex flex-wrap">
<section class="tbh-30a flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30a-stage" aria-label="Morph shape button">
<button type="button" class="cursor-pointer border-0 rounded px-[38px] py-4 text-base font-extrabold text-white bg-tbh30a-acc shadow-[0_10px_22px_-12px_color-mix(in_oklab,var(--color-tbh30a-acc)_70%,transparent)] transition-[border-radius,translate,scale,background-color] duration-500 ease-[cubic-bezier(.4,0,.2,1)] hover:rounded-[30px] hover:-translate-y-0.5 hover:bg-[color-mix(in_oklab,var(--color-tbh30a-acc)_88%,black)] focus-visible:rounded-[30px] focus-visible:-translate-y-0.5 active:translate-y-0 active:scale-[.98] focus-visible:outline-3 focus-visible:outline-tbh30a-acc focus-visible:outline-offset-4 motion-reduce:transition-[border-radius,background-color] motion-reduce:duration-[250ms] motion-reduce:hover:translate-y-0">
Send message
</button>
</section>
<section class="tbh-30b flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30b-stage" aria-label="Pill to notched button">
<button type="button" class="cursor-pointer border-0 rounded-[30px] px-10 py-4 text-base font-extrabold text-white bg-tbh30b-acc shadow-[0_12px_24px_-12px_var(--color-tbh30b-acc)] transition-[border-radius,translate,scale] duration-500 ease-[cubic-bezier(.4,0,.2,1)] hover:[border-radius:16px_4px_16px_4px] hover:-translate-y-0.5 focus-visible:[border-radius:16px_4px_16px_4px] focus-visible:-translate-y-0.5 active:translate-y-0 active:scale-[.98] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-[border-radius] motion-reduce:duration-[250ms] motion-reduce:hover:translate-y-0">
Reserve seat
</button>
</section>
<section class="tbh-30c flex-[1_1_340px] min-w-[300px] grid place-items-center px-6 py-14 font-tbh30-sans bg-tbh30c-stage" aria-label="Circle to bar button">
<button type="button" class="group/btn relative grid place-items-center overflow-hidden h-14 w-14 cursor-pointer border-0 rounded-[28px] text-base font-extrabold text-white bg-tbh30c-acc shadow-[0_12px_24px_-12px_var(--color-tbh30c-acc)] transition-[width,border-radius] duration-[450ms] ease-[cubic-bezier(.4,0,.2,1)] hover:w-[170px] hover:rounded-[14px] focus-visible:w-[170px] focus-visible:rounded-[14px] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-4 motion-reduce:transition-none">
<span aria-hidden="true" class="text-2xl leading-none transition-opacity duration-200 group-hover/btn:opacity-0 group-hover/btn:duration-150 group-focus-visible/btn:opacity-0">+</span>
<span class="absolute whitespace-nowrap opacity-0 transition-opacity duration-200 group-hover/btn:opacity-100 group-hover/btn:delay-[250ms] group-focus-visible/btn:opacity-100 group-focus-visible/btn:delay-[250ms]">Add item</span>
</button>
</section>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tbh30a-acc: #3d8fb8;
--color-tbh30a-acc: oklch(0.58 0.17 210);
--color-tbh30a-stage: #f4f6f8;
--color-tbh30a-stage: oklch(0.97 0.005 220);
--color-tbh30b-acc: #d4664a;
--color-tbh30b-acc: oklch(0.6 0.16 25);
--color-tbh30b-stage: #1a1010;
--color-tbh30b-stage: oklch(0.18 0.02 25);
--color-tbh30c-acc: #4aab72;
--color-tbh30c-acc: oklch(0.62 0.16 150);
--color-tbh30c-stage: #0c160f;
--color-tbh30c-stage: oklch(0.15 0.02 150);
--font-tbh30-sans: 'Space Grotesk', system-ui, sans-serif;
}
/* Nothing stays CSS: three radius targets and one width expansion. */
.tbh-30-group {
width: 100%;
min-height: 100svh;
}
.tbh-30a,
.tbh-30b,
.tbh-30c {
min-height: 100svh;
}
```How this works
Radius is the cheapest morph there is. Transitioning border-radius changes the silhouette without touching layout, so a square-to-pill morph costs nothing and never nudges a neighbour. Designs A and B are one utility each; the only real decision is what the target shape says.
Design C does animate width, on purpose. A 56px circle growing to 170px is a layout change, and that is acceptable here because the button is a floating action control with nothing beside it. The label timing is what makes it work: the glyph fades out fast (150ms) and the text arrives after a delay-[250ms], so the two never overlap mid-expansion.
Both endpoints must be intentional. A morph is a statement about two shapes. rounded (4px) to rounded-[30px] reads as 'formal becomes friendly'; the reverse reads as 'settle down'. Either is fine — drifting between two arbitrary radii is not.
Make it yours
- Direction: swap the resting and hover radii to morph pill → square.
- Partial morph:
hover:rounded-tl-[30px] hover:rounded-br-[30px]for a two-corner version. - Design C's expanded width:
hover:w-[170px]must fit the longest label — measure, do not guess, or the text clips. - Pair with the lift:
hover:-translate-y-0.5is on A and B; drop it for a flatter, more Swiss feel.
Gotchas — read before shipping
- Pad the label enough that a full pill radius does not clip the first and last glyphs —
px-[38px]is the floor at this font size. - Animate
border-radius, notwidth, for a shape change; design C animates width only because it is genuinely changing size, and it is the one that needsoverflow-hidden. - Design C's two labels are absolutely stacked, so the button needs an explicit height (
h-14) and the glyph must bearia-hiddenwith the real text always present in the DOM. - Under
motion-reduce:design C drops its transitions entirely: a reduced-motion user gets the expanded, labelled state on hover with no travel.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
border-radius transitions are universal; nothing here needs a fallback beyond Tailwind v4's own floor.