29 / 36
All 36 in 36 Tailwind CSS Modals
- 01Tailwind Login / Signup Modal with Native Dialog & Starting-Style
- 02Tailwind Password Reset Modal with In-Place Success State
- 03Tailwind Age Verification Gate with :has() and Zero JavaScript
- 04Tailwind Paywall Modal with Masked Article Fade
- 05Tailwind Product Quick-View Modal with has-checked Variant Chips
- 06Tailwind Shopping Cart Slide-Over Drawer with Dialog
- 07Tailwind Timed Newsletter Promo Popup with Frequency Cap
- 08Tailwind Size Guide Overlay with Zero-JS cm/inch Toggle
- 09Tailwind Zero-JS Lightbox Gallery with the target Variant
- 10Tailwind Video Trailer Modal with Aspect-Locked Iframe
- 11Tailwind Destructive Delete Modal with Type-to-Confirm Gate
- 12Tailwind Success / Error Result Modal with Drawn SVG Icon
- 13Tailwind GDPR Cookie Consent Banner with details Disclosure
- 14Tailwind Multi-Step Onboarding Modal with data-step Variants
- 15Tailwind Filter & Sort Drawer that Becomes a Bottom Sheet
- 16Tailwind Contact Support Modal with Live Validation
- 17Tailwind Tabbed Settings Modal with Zero-JS Radio Tabs
- 18Tailwind Command Palette Modal with ⌘K and Roving Selection
- 19Tailwind Confirm Delete Modal with Corner Slide-and-Spin Entrance
- 20Tailwind Login Modal with Staggered Blur-In Content Drop
- 21Tailwind Signup Modal with 3D Origami Fan Deploy Panels
- 22Tailwind Payment Checkout Modal with 3D Paper Fold Entrance
- 23Tailwind HIPAA Patient Consent Modal with Scroll-Gated Agreement
- 24Tailwind Age Verification Modal with Clip-Path Iris Aperture
- 25Tailwind GDPR Cookie Preferences Modal with Unfolding Category Rows
- 26Tailwind Video Lightbox Modal with Chromatic Glitch Materialize
- 27Tailwind Image Gallery Lightbox with Clip-Path Diamond Reveal
- 28Tailwind Add to Cart Confirmation with SVG Liquid Morph Entrance
- 29Tailwind Product Quick View Modal with Elastic Button Morph
- 30Tailwind Newsletter Signup Modal with Depth-Layer Parallax Entrance
- 31Tailwind Exit-Intent Discount Modal with Live Countdown and Coupon Copy
- 32Tailwind Restaurant Reservation Modal with Cascading Fan Panels
- 33Tailwind Order Success Modal with CSS-Only Confetti Burst
- 34Tailwind SaaS Onboarding Welcome Modal with Cascading Blur-In
- 35Tailwind Terms of Service Modal with Letter-Slide and Consent Gate
- 36Tailwind Password Reset Modal with Enumeration-Safe Confirmation
Tailwind Product Quick View Modal with Elastic Button Morph
A browse-mode product preview that expands from a button-sized pill into a full card — scale, translate and border-radius animating together on an elastic curve. The port states all three on the dialog as open:starting: values, so the morph's start shape lives beside its end shape.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tw-11 w-full min-h-svh grid place-items-center bg-tw11-bg font-tw11-sans px-6 py-10">
<button type="button" data-tw11-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg border border-tw11-ink bg-transparent text-[15px] font-semibold text-tw11-ink cursor-pointer transition-colors duration-200 hover:bg-tw11-ink hover:text-tw11-card focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">Quick view</button>
<dialog id="tw-11-dlg" aria-labelledby="tw-11-title" class="m-auto w-[min(720px,94vw)] p-0 border-0 overflow-hidden text-left text-tw11-ink bg-tw11-card shadow-[0_40px_60px_-20px_#1e293b59] will-change-transform opacity-0 scale-[0.08] translate-y-[200px] rounded-full transition-[opacity,scale,translate,border-radius,overlay,display] duration-[650ms] ease-[cubic-bezier(.68,-0.55,.32,1.55)] transition-discrete open:opacity-100 open:scale-100 open:translate-y-0 open:rounded-2xl open:starting:opacity-0 open:starting:scale-[0.08] open:starting:translate-y-[200px] open:starting:rounded-full open:backdrop:bg-tw11-scrim open:backdrop:backdrop-blur-[4px] motion-reduce:transition-[opacity] motion-reduce:duration-200 motion-reduce:scale-100 motion-reduce:translate-y-0 motion-reduce:rounded-2xl">
<button type="button" autofocus data-tw11-close aria-label="Close quick view" class="absolute top-3.5 right-3.5 z-10 grid place-items-center size-11 rounded-lg border-0 bg-tw11-glass backdrop-blur-[4px] text-[15px] text-tw11-sub cursor-pointer transition-colors duration-200 hover:bg-white hover:text-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">✕</button>
<div class="grid grid-cols-[1.1fr_1fr] max-[560px]:grid-cols-1">
<div data-tw11-gallery class="p-6">
<div data-tw11-hero class="grid place-items-center aspect-square rounded-xl text-[88px]" aria-hidden="true">👜</div>
<div role="radiogroup" aria-label="Colour" class="flex justify-center gap-2 mt-3.5">
<button type="button" role="radio" aria-checked="true" aria-label="Slate" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swslate ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Coral" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swcoral ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Olive" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swolive ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Mustard" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swmustard ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
</div>
</div>
<div class="flex flex-col px-[26px] pt-7 pb-6">
<p class="mb-1.5 text-xs font-bold uppercase tracking-[0.08em] text-tw11-sub">Terra Studio</p>
<h2 id="tw-11-title" class="mb-2.5 font-tw11-display text-2xl font-extrabold tracking-[-0.015em] leading-tight">Everyday Crossbody</h2>
<p class="flex items-baseline gap-2.5 mb-3">
<span class="text-[1.35rem] font-extrabold text-tw11-brand tabular-nums">$128</span>
<span class="text-sm text-tw11-sub line-through tabular-nums">$168</span>
</p>
<p class="mb-4 text-sm leading-relaxed text-tw11-sub text-pretty">Vegetable-tanned leather · Recycled brass hardware · Fits a 13-inch laptop.</p>
<div role="radiogroup" aria-label="Size" class="flex gap-2 mb-4">
<button type="button" role="radio" aria-checked="false" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">S</button>
<button type="button" role="radio" aria-checked="true" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">M</button>
<button type="button" role="radio" aria-checked="false" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">L</button>
</div>
<button type="button" data-tw11-close class="mt-auto min-h-11 px-5 rounded-[9px] border-0 bg-tw11-brand text-[15px] font-bold text-white tabular-nums shadow-[0_8px_18px_-6px_var(--color-tw11-glow)] cursor-pointer transition-[background-color,translate] duration-200 hover:bg-tw11-branddark hover:-translate-y-px focus-visible:outline-2 focus-visible:outline-tw11-brand focus-visible:outline-offset-[3px] motion-reduce:hover:translate-y-0">Add to cart · $128</button>
<a href="#" class="mt-2.5 text-center text-[13px] text-tw11-sub no-underline hover:text-tw11-ink hover:underline">View full details →</a>
</div>
</div>
</dialog>
</section><section class="tw-11 w-full min-h-svh grid place-items-center bg-tw11-bg font-tw11-sans px-6 py-10">
<button type="button" data-tw11-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg border border-tw11-ink bg-transparent text-[15px] font-semibold text-tw11-ink cursor-pointer transition-colors duration-200 hover:bg-tw11-ink hover:text-tw11-card focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">Quick view</button>
<dialog id="tw-11-dlg" aria-labelledby="tw-11-title" class="m-auto w-[min(720px,94vw)] p-0 border-0 overflow-hidden text-left text-tw11-ink bg-tw11-card shadow-[0_40px_60px_-20px_#1e293b59] will-change-transform opacity-0 scale-[0.08] translate-y-[200px] rounded-full transition-[opacity,scale,translate,border-radius,overlay,display] duration-[650ms] ease-[cubic-bezier(.68,-0.55,.32,1.55)] transition-discrete open:opacity-100 open:scale-100 open:translate-y-0 open:rounded-2xl open:starting:opacity-0 open:starting:scale-[0.08] open:starting:translate-y-[200px] open:starting:rounded-full open:backdrop:bg-tw11-scrim open:backdrop:backdrop-blur-[4px] motion-reduce:transition-[opacity] motion-reduce:duration-200 motion-reduce:scale-100 motion-reduce:translate-y-0 motion-reduce:rounded-2xl">
<button type="button" autofocus data-tw11-close aria-label="Close quick view" class="absolute top-3.5 right-3.5 z-10 grid place-items-center size-11 rounded-lg border-0 bg-tw11-glass backdrop-blur-[4px] text-[15px] text-tw11-sub cursor-pointer transition-colors duration-200 hover:bg-white hover:text-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">✕</button>
<div class="grid grid-cols-[1.1fr_1fr] max-[560px]:grid-cols-1">
<div data-tw11-gallery class="p-6">
<div data-tw11-hero class="grid place-items-center aspect-square rounded-xl text-[88px]" aria-hidden="true">👜</div>
<div role="radiogroup" aria-label="Colour" class="flex justify-center gap-2 mt-3.5">
<button type="button" role="radio" aria-checked="true" aria-label="Slate" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swslate ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Coral" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swcoral ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Olive" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swolive ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Mustard" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swmustard ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
</div>
</div>
<div class="flex flex-col px-[26px] pt-7 pb-6">
<p class="mb-1.5 text-xs font-bold uppercase tracking-[0.08em] text-tw11-sub">Terra Studio</p>
<h2 id="tw-11-title" class="mb-2.5 font-tw11-display text-2xl font-extrabold tracking-[-0.015em] leading-tight">Everyday Crossbody</h2>
<p class="flex items-baseline gap-2.5 mb-3">
<span class="text-[1.35rem] font-extrabold text-tw11-brand tabular-nums">$128</span>
<span class="text-sm text-tw11-sub line-through tabular-nums">$168</span>
</p>
<p class="mb-4 text-sm leading-relaxed text-tw11-sub text-pretty">Vegetable-tanned leather · Recycled brass hardware · Fits a 13-inch laptop.</p>
<div role="radiogroup" aria-label="Size" class="flex gap-2 mb-4">
<button type="button" role="radio" aria-checked="false" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">S</button>
<button type="button" role="radio" aria-checked="true" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">M</button>
<button type="button" role="radio" aria-checked="false" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">L</button>
</div>
<button type="button" data-tw11-close class="mt-auto min-h-11 px-5 rounded-[9px] border-0 bg-tw11-brand text-[15px] font-bold text-white tabular-nums shadow-[0_8px_18px_-6px_var(--color-tw11-glow)] cursor-pointer transition-[background-color,translate] duration-200 hover:bg-tw11-branddark hover:-translate-y-px focus-visible:outline-2 focus-visible:outline-tw11-brand focus-visible:outline-offset-[3px] motion-reduce:hover:translate-y-0">Add to cart · $128</button>
<a href="#" class="mt-2.5 text-center text-[13px] text-tw11-sub no-underline hover:text-tw11-ink hover:underline">View full details →</a>
</div>
</div>
</dialog>
</section>@import "tailwindcss";
@theme {
--color-tw11-bg: #fdf7f4;
--color-tw11-bg: oklch(0.97 0.012 40);
--color-tw11-card: #fafaf7;
--color-tw11-card: oklch(0.98 0.004 95);
--color-tw11-ink: #1e293b;
--color-tw11-ink: oklch(0.3 0.03 258);
--color-tw11-sub: #55657c;
--color-tw11-sub: oklch(0.5 0.03 255);
--color-tw11-line: #e5e0d5;
--color-tw11-line: oklch(0.9 0.015 85);
--color-tw11-brand: #e05252;
--color-tw11-brand: oklch(0.62 0.17 22);
--color-tw11-branddark: #c23e3e;
--color-tw11-branddark: oklch(0.55 0.17 22);
--color-tw11-glow: #ff6b6b80;
--color-tw11-glow: oklch(0.7 0.17 22 / 0.5);
--color-tw11-glass: #ffffffb3;
--color-tw11-glass: oklch(1 0 0 / 0.7);
--color-tw11-swslate: #1e293b;
--color-tw11-swslate: oklch(0.3 0.03 258);
--color-tw11-swcoral: #ff6b6b;
--color-tw11-swcoral: oklch(0.7 0.17 22);
--color-tw11-swolive: #84cc16;
--color-tw11-swolive: oklch(0.77 0.19 128);
--color-tw11-swmustard: #eab308;
--color-tw11-swmustard: oklch(0.79 0.16 86);
--color-tw11-scrim: #1e293b80;
--color-tw11-scrim: oklch(0.3 0.03 258 / 0.5);
--font-tw11-sans: 'Outfit', system-ui, sans-serif;
--font-tw11-display: 'Playfair Display', Georgia, serif;
}
.tw-11 {
width: 100%;
}
/* Placeholder gallery surfaces — swap for real product photography. */
.tw-11 [data-tw11-gallery] {
background-image: linear-gradient(180deg, oklch(0.95 0.03 85), oklch(0.92 0.04 80));
}
.tw-11 [data-tw11-hero] {
background-image: linear-gradient(135deg, oklch(0.93 0.04 350), oklch(0.88 0.06 280));
}
#tw-11-dlg::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
transition: background .3s ease, backdrop-filter .3s ease, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tw-11-dlg[open]::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-11-dlg::backdrop {
transition-duration: .001s;
}
}@import "tailwindcss";
@theme {
--color-tw11-bg: #fdf7f4;
--color-tw11-bg: oklch(0.97 0.012 40);
--color-tw11-card: #fafaf7;
--color-tw11-card: oklch(0.98 0.004 95);
--color-tw11-ink: #1e293b;
--color-tw11-ink: oklch(0.3 0.03 258);
--color-tw11-sub: #55657c;
--color-tw11-sub: oklch(0.5 0.03 255);
--color-tw11-line: #e5e0d5;
--color-tw11-line: oklch(0.9 0.015 85);
--color-tw11-brand: #e05252;
--color-tw11-brand: oklch(0.62 0.17 22);
--color-tw11-branddark: #c23e3e;
--color-tw11-branddark: oklch(0.55 0.17 22);
--color-tw11-glow: #ff6b6b80;
--color-tw11-glow: oklch(0.7 0.17 22 / 0.5);
--color-tw11-glass: #ffffffb3;
--color-tw11-glass: oklch(1 0 0 / 0.7);
--color-tw11-swslate: #1e293b;
--color-tw11-swslate: oklch(0.3 0.03 258);
--color-tw11-swcoral: #ff6b6b;
--color-tw11-swcoral: oklch(0.7 0.17 22);
--color-tw11-swolive: #84cc16;
--color-tw11-swolive: oklch(0.77 0.19 128);
--color-tw11-swmustard: #eab308;
--color-tw11-swmustard: oklch(0.79 0.16 86);
--color-tw11-scrim: #1e293b80;
--color-tw11-scrim: oklch(0.3 0.03 258 / 0.5);
--font-tw11-sans: 'Outfit', system-ui, sans-serif;
--font-tw11-display: 'Playfair Display', Georgia, serif;
}
.tw-11 {
width: 100%;
}
/* Placeholder gallery surfaces — swap for real product photography. */
.tw-11 [data-tw11-gallery] {
background-image: linear-gradient(180deg, oklch(0.95 0.03 85), oklch(0.92 0.04 80));
}
.tw-11 [data-tw11-hero] {
background-image: linear-gradient(135deg, oklch(0.93 0.04 350), oklch(0.88 0.06 280));
}
#tw-11-dlg::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
transition: background .3s ease, backdrop-filter .3s ease, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tw-11-dlg[open]::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-11-dlg::backdrop {
transition-duration: .001s;
}
}(function () {
var root = document.querySelector('.tw-11');
var dlg = root.querySelector('#tw-11-dlg');
root.querySelector('[data-tw11-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw11-close]').forEach(function (b) {
b.addEventListener('click', function () { dlg.close(); });
});
// Radio groups: one checked per group, state written to ARIA so styling follows semantics.
root.querySelectorAll('[role=radiogroup]').forEach(function (group) {
group.addEventListener('click', function (e) {
var btn = e.target.closest('[role=radio]');
if (!btn) return;
group.querySelectorAll('[role=radio]').forEach(function (r) { r.setAttribute('aria-checked', String(r === btn)); });
});
});
dlg.addEventListener('click', function (e) {
var r = dlg.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();(function () {
var root = document.querySelector('.tw-11');
var dlg = root.querySelector('#tw-11-dlg');
root.querySelector('[data-tw11-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw11-close]').forEach(function (b) {
b.addEventListener('click', function () { dlg.close(); });
});
// Radio groups: one checked per group, state written to ARIA so styling follows semantics.
root.querySelectorAll('[role=radiogroup]').forEach(function (group) {
group.addEventListener('click', function (e) {
var btn = e.target.closest('[role=radio]');
if (!btn) return;
group.querySelectorAll('[role=radio]').forEach(function (r) { r.setAttribute('aria-checked', String(r === btn)); });
});
});
dlg.addEventListener('click', function (e) {
var r = dlg.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();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-tw11-bg: #fdf7f4;
--color-tw11-bg: oklch(0.97 0.012 40);
--color-tw11-card: #fafaf7;
--color-tw11-card: oklch(0.98 0.004 95);
--color-tw11-ink: #1e293b;
--color-tw11-ink: oklch(0.3 0.03 258);
--color-tw11-sub: #55657c;
--color-tw11-sub: oklch(0.5 0.03 255);
--color-tw11-line: #e5e0d5;
--color-tw11-line: oklch(0.9 0.015 85);
--color-tw11-brand: #e05252;
--color-tw11-brand: oklch(0.62 0.17 22);
--color-tw11-branddark: #c23e3e;
--color-tw11-branddark: oklch(0.55 0.17 22);
--color-tw11-glow: #ff6b6b80;
--color-tw11-glow: oklch(0.7 0.17 22 / 0.5);
--color-tw11-glass: #ffffffb3;
--color-tw11-glass: oklch(1 0 0 / 0.7);
--color-tw11-swslate: #1e293b;
--color-tw11-swslate: oklch(0.3 0.03 258);
--color-tw11-swcoral: #ff6b6b;
--color-tw11-swcoral: oklch(0.7 0.17 22);
--color-tw11-swolive: #84cc16;
--color-tw11-swolive: oklch(0.77 0.19 128);
--color-tw11-swmustard: #eab308;
--color-tw11-swmustard: oklch(0.79 0.16 86);
--color-tw11-scrim: #1e293b80;
--color-tw11-scrim: oklch(0.3 0.03 258 / 0.5);
--font-tw11-sans: 'Outfit', system-ui, sans-serif;
--font-tw11-display: 'Playfair Display', Georgia, serif;
}
.tw-11 {
width: 100%;
}
/* Placeholder gallery surfaces — swap for real product photography. */
.tw-11 [data-tw11-gallery] {
background-image: linear-gradient(180deg, oklch(0.95 0.03 85), oklch(0.92 0.04 80));
}
.tw-11 [data-tw11-hero] {
background-image: linear-gradient(135deg, oklch(0.93 0.04 350), oklch(0.88 0.06 280));
}
#tw-11-dlg::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
transition: background .3s ease, backdrop-filter .3s ease, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tw-11-dlg[open]::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-11-dlg::backdrop {
transition-duration: .001s;
}
}@import "tailwindcss";
@theme {
--color-tw11-bg: #fdf7f4;
--color-tw11-bg: oklch(0.97 0.012 40);
--color-tw11-card: #fafaf7;
--color-tw11-card: oklch(0.98 0.004 95);
--color-tw11-ink: #1e293b;
--color-tw11-ink: oklch(0.3 0.03 258);
--color-tw11-sub: #55657c;
--color-tw11-sub: oklch(0.5 0.03 255);
--color-tw11-line: #e5e0d5;
--color-tw11-line: oklch(0.9 0.015 85);
--color-tw11-brand: #e05252;
--color-tw11-brand: oklch(0.62 0.17 22);
--color-tw11-branddark: #c23e3e;
--color-tw11-branddark: oklch(0.55 0.17 22);
--color-tw11-glow: #ff6b6b80;
--color-tw11-glow: oklch(0.7 0.17 22 / 0.5);
--color-tw11-glass: #ffffffb3;
--color-tw11-glass: oklch(1 0 0 / 0.7);
--color-tw11-swslate: #1e293b;
--color-tw11-swslate: oklch(0.3 0.03 258);
--color-tw11-swcoral: #ff6b6b;
--color-tw11-swcoral: oklch(0.7 0.17 22);
--color-tw11-swolive: #84cc16;
--color-tw11-swolive: oklch(0.77 0.19 128);
--color-tw11-swmustard: #eab308;
--color-tw11-swmustard: oklch(0.79 0.16 86);
--color-tw11-scrim: #1e293b80;
--color-tw11-scrim: oklch(0.3 0.03 258 / 0.5);
--font-tw11-sans: 'Outfit', system-ui, sans-serif;
--font-tw11-display: 'Playfair Display', Georgia, serif;
}
.tw-11 {
width: 100%;
}
/* Placeholder gallery surfaces — swap for real product photography. */
.tw-11 [data-tw11-gallery] {
background-image: linear-gradient(180deg, oklch(0.95 0.03 85), oklch(0.92 0.04 80));
}
.tw-11 [data-tw11-hero] {
background-image: linear-gradient(135deg, oklch(0.93 0.04 350), oklch(0.88 0.06 280));
}
#tw-11-dlg::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
transition: background .3s ease, backdrop-filter .3s ease, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tw-11-dlg[open]::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-11-dlg::backdrop {
transition-duration: .001s;
}
}<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=Outfit:wght@400;500;600;700;800&family=Playfair+Display:wght@700;800&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=Outfit:wght@400;500;600;700;800&family=Playfair+Display:wght@700;800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Four properties, one curve, one class list: the morph's start shape (
scale-[0.08] translate-y-[200px] rounded-full) is stated asopen:starting:right beside the card's resting shape. - Because the dialog itself is the card here, the light-dismiss test uses the dialog's own rect — a click inside the modal box is always inside the card.
- Selection styling hangs off
aria-[checked=true], so the controller sets one attribute and both the visual state and the announced state move together. will-change-transformis kept from the original for a real reason: at 8% scale the compositor needs the hint or the first frames render unpromoted.- UI change from the CSS original: swatches sit inside 44px buttons (the coloured dot is a child, so it can stay 24px), sizes are 44px chips, and the close control grew to 44px.
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 Product Quick View Modal with Elastic Button Morph
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-quick-view-modal-morphing-button-elastic/
A browse-mode product preview that expands from a button-sized pill into a full card — scale, translate and border-radius animating together on an elastic curve. The port states all three on the dialog as open:starting: values, so the morph's start shape lives beside its end shape.
## HTML
```html
<section class="tw-11 w-full min-h-svh grid place-items-center bg-tw11-bg font-tw11-sans px-6 py-10">
<button type="button" data-tw11-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg border border-tw11-ink bg-transparent text-[15px] font-semibold text-tw11-ink cursor-pointer transition-colors duration-200 hover:bg-tw11-ink hover:text-tw11-card focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">Quick view</button>
<dialog id="tw-11-dlg" aria-labelledby="tw-11-title" class="m-auto w-[min(720px,94vw)] p-0 border-0 overflow-hidden text-left text-tw11-ink bg-tw11-card shadow-[0_40px_60px_-20px_#1e293b59] will-change-transform opacity-0 scale-[0.08] translate-y-[200px] rounded-full transition-[opacity,scale,translate,border-radius,overlay,display] duration-[650ms] ease-[cubic-bezier(.68,-0.55,.32,1.55)] transition-discrete open:opacity-100 open:scale-100 open:translate-y-0 open:rounded-2xl open:starting:opacity-0 open:starting:scale-[0.08] open:starting:translate-y-[200px] open:starting:rounded-full open:backdrop:bg-tw11-scrim open:backdrop:backdrop-blur-[4px] motion-reduce:transition-[opacity] motion-reduce:duration-200 motion-reduce:scale-100 motion-reduce:translate-y-0 motion-reduce:rounded-2xl">
<button type="button" autofocus data-tw11-close aria-label="Close quick view" class="absolute top-3.5 right-3.5 z-10 grid place-items-center size-11 rounded-lg border-0 bg-tw11-glass backdrop-blur-[4px] text-[15px] text-tw11-sub cursor-pointer transition-colors duration-200 hover:bg-white hover:text-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">✕</button>
<div class="grid grid-cols-[1.1fr_1fr] max-[560px]:grid-cols-1">
<div data-tw11-gallery class="p-6">
<div data-tw11-hero class="grid place-items-center aspect-square rounded-xl text-[88px]" aria-hidden="true">👜</div>
<div role="radiogroup" aria-label="Colour" class="flex justify-center gap-2 mt-3.5">
<button type="button" role="radio" aria-checked="true" aria-label="Slate" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swslate ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Coral" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swcoral ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Olive" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swolive ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Mustard" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swmustard ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
</div>
</div>
<div class="flex flex-col px-[26px] pt-7 pb-6">
<p class="mb-1.5 text-xs font-bold uppercase tracking-[0.08em] text-tw11-sub">Terra Studio</p>
<h2 id="tw-11-title" class="mb-2.5 font-tw11-display text-2xl font-extrabold tracking-[-0.015em] leading-tight">Everyday Crossbody</h2>
<p class="flex items-baseline gap-2.5 mb-3">
<span class="text-[1.35rem] font-extrabold text-tw11-brand tabular-nums">$128</span>
<span class="text-sm text-tw11-sub line-through tabular-nums">$168</span>
</p>
<p class="mb-4 text-sm leading-relaxed text-tw11-sub text-pretty">Vegetable-tanned leather · Recycled brass hardware · Fits a 13-inch laptop.</p>
<div role="radiogroup" aria-label="Size" class="flex gap-2 mb-4">
<button type="button" role="radio" aria-checked="false" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">S</button>
<button type="button" role="radio" aria-checked="true" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">M</button>
<button type="button" role="radio" aria-checked="false" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">L</button>
</div>
<button type="button" data-tw11-close class="mt-auto min-h-11 px-5 rounded-[9px] border-0 bg-tw11-brand text-[15px] font-bold text-white tabular-nums shadow-[0_8px_18px_-6px_var(--color-tw11-glow)] cursor-pointer transition-[background-color,translate] duration-200 hover:bg-tw11-branddark hover:-translate-y-px focus-visible:outline-2 focus-visible:outline-tw11-brand focus-visible:outline-offset-[3px] motion-reduce:hover:translate-y-0">Add to cart · $128</button>
<a href="#" class="mt-2.5 text-center text-[13px] text-tw11-sub no-underline hover:text-tw11-ink hover:underline">View full details →</a>
</div>
</div>
</dialog>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tw11-bg: #fdf7f4;
--color-tw11-bg: oklch(0.97 0.012 40);
--color-tw11-card: #fafaf7;
--color-tw11-card: oklch(0.98 0.004 95);
--color-tw11-ink: #1e293b;
--color-tw11-ink: oklch(0.3 0.03 258);
--color-tw11-sub: #55657c;
--color-tw11-sub: oklch(0.5 0.03 255);
--color-tw11-line: #e5e0d5;
--color-tw11-line: oklch(0.9 0.015 85);
--color-tw11-brand: #e05252;
--color-tw11-brand: oklch(0.62 0.17 22);
--color-tw11-branddark: #c23e3e;
--color-tw11-branddark: oklch(0.55 0.17 22);
--color-tw11-glow: #ff6b6b80;
--color-tw11-glow: oklch(0.7 0.17 22 / 0.5);
--color-tw11-glass: #ffffffb3;
--color-tw11-glass: oklch(1 0 0 / 0.7);
--color-tw11-swslate: #1e293b;
--color-tw11-swslate: oklch(0.3 0.03 258);
--color-tw11-swcoral: #ff6b6b;
--color-tw11-swcoral: oklch(0.7 0.17 22);
--color-tw11-swolive: #84cc16;
--color-tw11-swolive: oklch(0.77 0.19 128);
--color-tw11-swmustard: #eab308;
--color-tw11-swmustard: oklch(0.79 0.16 86);
--color-tw11-scrim: #1e293b80;
--color-tw11-scrim: oklch(0.3 0.03 258 / 0.5);
--font-tw11-sans: 'Outfit', system-ui, sans-serif;
--font-tw11-display: 'Playfair Display', Georgia, serif;
}
.tw-11 {
width: 100%;
}
/* Placeholder gallery surfaces — swap for real product photography. */
.tw-11 [data-tw11-gallery] {
background-image: linear-gradient(180deg, oklch(0.95 0.03 85), oklch(0.92 0.04 80));
}
.tw-11 [data-tw11-hero] {
background-image: linear-gradient(135deg, oklch(0.93 0.04 350), oklch(0.88 0.06 280));
}
#tw-11-dlg::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
transition: background .3s ease, backdrop-filter .3s ease, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tw-11-dlg[open]::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-11-dlg::backdrop {
transition-duration: .001s;
}
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.tw-11');
var dlg = root.querySelector('#tw-11-dlg');
root.querySelector('[data-tw11-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw11-close]').forEach(function (b) {
b.addEventListener('click', function () { dlg.close(); });
});
// Radio groups: one checked per group, state written to ARIA so styling follows semantics.
root.querySelectorAll('[role=radiogroup]').forEach(function (group) {
group.addEventListener('click', function (e) {
var btn = e.target.closest('[role=radio]');
if (!btn) return;
group.querySelectorAll('[role=radio]').forEach(function (r) { r.setAttribute('aria-checked', String(r === btn)); });
});
});
dlg.addEventListener('click', function (e) {
var r = dlg.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();
```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 Product Quick View Modal with Elastic Button Morph
Source: https://codefronts.com/components/tailwind-css-modals/tailwind-quick-view-modal-morphing-button-elastic/
A browse-mode product preview that expands from a button-sized pill into a full card — scale, translate and border-radius animating together on an elastic curve. The port states all three on the dialog as open:starting: values, so the morph's start shape lives beside its end shape.
## HTML
```html
<section class="tw-11 w-full min-h-svh grid place-items-center bg-tw11-bg font-tw11-sans px-6 py-10">
<button type="button" data-tw11-open class="inline-flex items-center justify-center min-h-11 px-6 rounded-lg border border-tw11-ink bg-transparent text-[15px] font-semibold text-tw11-ink cursor-pointer transition-colors duration-200 hover:bg-tw11-ink hover:text-tw11-card focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">Quick view</button>
<dialog id="tw-11-dlg" aria-labelledby="tw-11-title" class="m-auto w-[min(720px,94vw)] p-0 border-0 overflow-hidden text-left text-tw11-ink bg-tw11-card shadow-[0_40px_60px_-20px_#1e293b59] will-change-transform opacity-0 scale-[0.08] translate-y-[200px] rounded-full transition-[opacity,scale,translate,border-radius,overlay,display] duration-[650ms] ease-[cubic-bezier(.68,-0.55,.32,1.55)] transition-discrete open:opacity-100 open:scale-100 open:translate-y-0 open:rounded-2xl open:starting:opacity-0 open:starting:scale-[0.08] open:starting:translate-y-[200px] open:starting:rounded-full open:backdrop:bg-tw11-scrim open:backdrop:backdrop-blur-[4px] motion-reduce:transition-[opacity] motion-reduce:duration-200 motion-reduce:scale-100 motion-reduce:translate-y-0 motion-reduce:rounded-2xl">
<button type="button" autofocus data-tw11-close aria-label="Close quick view" class="absolute top-3.5 right-3.5 z-10 grid place-items-center size-11 rounded-lg border-0 bg-tw11-glass backdrop-blur-[4px] text-[15px] text-tw11-sub cursor-pointer transition-colors duration-200 hover:bg-white hover:text-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">✕</button>
<div class="grid grid-cols-[1.1fr_1fr] max-[560px]:grid-cols-1">
<div data-tw11-gallery class="p-6">
<div data-tw11-hero class="grid place-items-center aspect-square rounded-xl text-[88px]" aria-hidden="true">👜</div>
<div role="radiogroup" aria-label="Colour" class="flex justify-center gap-2 mt-3.5">
<button type="button" role="radio" aria-checked="true" aria-label="Slate" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swslate ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Coral" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swcoral ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Olive" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swolive ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
<button type="button" role="radio" aria-checked="false" aria-label="Mustard" class="grid place-items-center size-11 rounded-full cursor-pointer bg-transparent border-0 focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2"><span class="block size-6 rounded-full bg-tw11-swmustard ring-1 ring-tw11-line transition-[scale,box-shadow] duration-200 [button[aria-checked=true]_&]:scale-110 [button[aria-checked=true]_&]:ring-2 [button[aria-checked=true]_&]:ring-tw11-ink"></span></button>
</div>
</div>
<div class="flex flex-col px-[26px] pt-7 pb-6">
<p class="mb-1.5 text-xs font-bold uppercase tracking-[0.08em] text-tw11-sub">Terra Studio</p>
<h2 id="tw-11-title" class="mb-2.5 font-tw11-display text-2xl font-extrabold tracking-[-0.015em] leading-tight">Everyday Crossbody</h2>
<p class="flex items-baseline gap-2.5 mb-3">
<span class="text-[1.35rem] font-extrabold text-tw11-brand tabular-nums">$128</span>
<span class="text-sm text-tw11-sub line-through tabular-nums">$168</span>
</p>
<p class="mb-4 text-sm leading-relaxed text-tw11-sub text-pretty">Vegetable-tanned leather · Recycled brass hardware · Fits a 13-inch laptop.</p>
<div role="radiogroup" aria-label="Size" class="flex gap-2 mb-4">
<button type="button" role="radio" aria-checked="false" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">S</button>
<button type="button" role="radio" aria-checked="true" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">M</button>
<button type="button" role="radio" aria-checked="false" class="min-w-11 min-h-11 px-3.5 rounded-lg border border-tw11-line bg-white text-sm font-bold text-tw11-ink cursor-pointer transition-colors duration-200 hover:border-tw11-ink aria-[checked=true]:bg-tw11-ink aria-[checked=true]:text-tw11-card aria-[checked=true]:border-tw11-ink focus-visible:outline-2 focus-visible:outline-tw11-ink focus-visible:outline-offset-2">L</button>
</div>
<button type="button" data-tw11-close class="mt-auto min-h-11 px-5 rounded-[9px] border-0 bg-tw11-brand text-[15px] font-bold text-white tabular-nums shadow-[0_8px_18px_-6px_var(--color-tw11-glow)] cursor-pointer transition-[background-color,translate] duration-200 hover:bg-tw11-branddark hover:-translate-y-px focus-visible:outline-2 focus-visible:outline-tw11-brand focus-visible:outline-offset-[3px] motion-reduce:hover:translate-y-0">Add to cart · $128</button>
<a href="#" class="mt-2.5 text-center text-[13px] text-tw11-sub no-underline hover:text-tw11-ink hover:underline">View full details →</a>
</div>
</div>
</dialog>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tw11-bg: #fdf7f4;
--color-tw11-bg: oklch(0.97 0.012 40);
--color-tw11-card: #fafaf7;
--color-tw11-card: oklch(0.98 0.004 95);
--color-tw11-ink: #1e293b;
--color-tw11-ink: oklch(0.3 0.03 258);
--color-tw11-sub: #55657c;
--color-tw11-sub: oklch(0.5 0.03 255);
--color-tw11-line: #e5e0d5;
--color-tw11-line: oklch(0.9 0.015 85);
--color-tw11-brand: #e05252;
--color-tw11-brand: oklch(0.62 0.17 22);
--color-tw11-branddark: #c23e3e;
--color-tw11-branddark: oklch(0.55 0.17 22);
--color-tw11-glow: #ff6b6b80;
--color-tw11-glow: oklch(0.7 0.17 22 / 0.5);
--color-tw11-glass: #ffffffb3;
--color-tw11-glass: oklch(1 0 0 / 0.7);
--color-tw11-swslate: #1e293b;
--color-tw11-swslate: oklch(0.3 0.03 258);
--color-tw11-swcoral: #ff6b6b;
--color-tw11-swcoral: oklch(0.7 0.17 22);
--color-tw11-swolive: #84cc16;
--color-tw11-swolive: oklch(0.77 0.19 128);
--color-tw11-swmustard: #eab308;
--color-tw11-swmustard: oklch(0.79 0.16 86);
--color-tw11-scrim: #1e293b80;
--color-tw11-scrim: oklch(0.3 0.03 258 / 0.5);
--font-tw11-sans: 'Outfit', system-ui, sans-serif;
--font-tw11-display: 'Playfair Display', Georgia, serif;
}
.tw-11 {
width: 100%;
}
/* Placeholder gallery surfaces — swap for real product photography. */
.tw-11 [data-tw11-gallery] {
background-image: linear-gradient(180deg, oklch(0.95 0.03 85), oklch(0.92 0.04 80));
}
.tw-11 [data-tw11-hero] {
background-image: linear-gradient(135deg, oklch(0.93 0.04 350), oklch(0.88 0.06 280));
}
#tw-11-dlg::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
transition: background .3s ease, backdrop-filter .3s ease, overlay .3s allow-discrete, display .3s allow-discrete;
}
@starting-style {
#tw-11-dlg[open]::backdrop {
background: oklch(0.3 0.03 258 / 0);
backdrop-filter: blur(0px);
}
}
@media (prefers-reduced-motion: reduce) {
#tw-11-dlg::backdrop {
transition-duration: .001s;
}
}
```
## JavaScript
```js
(function () {
var root = document.querySelector('.tw-11');
var dlg = root.querySelector('#tw-11-dlg');
root.querySelector('[data-tw11-open]').addEventListener('click', function () { dlg.showModal(); });
root.querySelectorAll('[data-tw11-close]').forEach(function (b) {
b.addEventListener('click', function () { dlg.close(); });
});
// Radio groups: one checked per group, state written to ARIA so styling follows semantics.
root.querySelectorAll('[role=radiogroup]').forEach(function (group) {
group.addEventListener('click', function (e) {
var btn = e.target.closest('[role=radio]');
if (!btn) return;
group.querySelectorAll('[role=radio]').forEach(function (r) { r.setAttribute('aria-checked', String(r === btn)); });
});
});
dlg.addEventListener('click', function (e) {
var r = dlg.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) dlg.close();
});
})();
```How this works
The morph is four properties on one curve. Scale, translate, radius and opacity all transition together; the elastic cubic-bezier(.68,-.55,.32,1.55) undershoots then overshoots, which is what sells "button becomes card".
Selection state is ARIA-first. The colour swatches and sizes are buttons in a role="radiogroup"; their styling reads aria-[checked=true]:, so the visual and the announced state cannot disagree.
Focus starts on close. Quick view is exploratory: the most common next action is dismissing to keep browsing, and Add to cart is two Tabs away.
Make it yours
- Calm the motion for premium brands: swap the elastic curve for
cubic-bezier(.34,1.36,.44,1), or drop to a fade and scale. - Move the morph origin with
origin-bottom-rightwhen the trigger is a floating button rather than a grid card. - Wire swatches to swap the hero image; for patterned goods swap the
srcrather than a gradient. - Add a wishlist toggle beside Add to cart — the actions row is already a grid.
Gotchas — read before shipping
- An 0.08 start scale renders text about a pixel tall for a frame. Keep
will-change: transform(here on the card) so the compositor promotes the layer and interpolates smoothly. - Elastic undershoot is precisely the motion vestibular-sensitive users react to — the
motion-reduce:path must be a plain fade, not a slower bounce. - Quick view is for browse-mode categories. High-consideration purchases need the full product page, and a preview that replaces it costs conversions rather than saving clicks.
- Add to cart from a quick view must lead somewhere — a silent close leaves the shopper unsure anything happened.
- Swatch and size chips need real 44px targets; decorative 24px dots are unusable on touch and invisible to coarse pointers.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ · 117+ anim | 16.4+ · 17.5+ anim | 128+ · 129+ anim | 111+ · 117+ anim |
transform and border-radius transitions are universal; the @starting-style morph is the newer gate and degrades to an instant show.