04 / 20
All 20 in 20 Tailwind CSS Popovers
- 01Tailwind Popover Show and Hide with the Popover API
- 02Tailwind Auto vs Manual Popover Behavior
- 03Tailwind Anchored Popover with CSS Anchor Positioning
- 04Tailwind Popover Entry and Exit Animation
- 05Tailwind Popover Backdrop and Light Dismiss
- 06Tailwind Position Area Popover Placement Grid
- 07Tailwind Popover Flip with Position Try Fallbacks
- 08Tailwind Tooltip Arrow Pointing to Anchor
- 09Tailwind Dropdown Matching Anchor Width
- 10Tailwind Nested Popover Submenu
- 11Tailwind Custom Select Listbox Popover
- 12Tailwind Tooltip on Hover and Focus
- 13Tailwind User Profile Dropdown Menu
- 14Tailwind Date Picker Popover
- 15Tailwind Filter Dropdown with Checkboxes
- 16Tailwind Popover Fallback and Polyfill
- 17Tailwind Popover vs Dialog Modal
- 18Tailwind Glassmorphism Popover
- 19Tailwind Dark Mode Popover Toggle
- 20Tailwind Accessible Tooltip with ARIA
Tailwind Popover Entry and Exit Animation
The three-part recipe almost everyone ships two-thirds of, as utilities: starting:open: for the entry values, transition-discrete so display animates, and overlay in the transition list so the panel stays in the top layer while it leaves. The port adds an asymmetric timing — 260ms in, 140ms out — with nothing more than duration-140 open:duration-260.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tpop-04 group/root w-full min-h-svh bg-tpop04-void bg-[radial-gradient(60%_45%_at_50%_40%,var(--color-tpop04-halo),transparent)] px-4.5 py-10 font-tpop04-sans text-tpop04-ink" aria-label="Popover entry and exit animation demo">
<div class="grid min-h-[calc(100svh-80px)] max-w-full place-items-center">
<div class="w-full min-w-0 max-w-[27rem] rounded-2xl border border-tpop04-rule bg-tpop04-surface px-4.5 pt-4.5 pb-4 shadow-[0_40px_80px_-40px_oklch(0_0_0/0.9)]">
<div class="flex min-w-0 items-center gap-2.5">
<span aria-hidden="true" class="size-2.5 flex-none rounded-full bg-tpop04-glow shadow-[0_0_12px_var(--color-tpop04-glow)]"></span>
<p class="min-w-0 flex-auto font-tpop04-mono text-[14px] font-semibold leading-tight">Build 4821 · main</p>
<span class="font-tpop04-mono text-[10.5px] uppercase tracking-[0.14em] text-tpop04-mut after:content-['closed'] group-has-[#tpop-04-panel:popover-open]/root:text-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:after:content-['open']" aria-hidden="true"></span>
<button id="tpop-04-trigger" type="button" popovertarget="tpop-04-panel" aria-label="Build details" class="inline-flex size-11 flex-none cursor-pointer items-center justify-center rounded-full border border-tpop04-rule text-tpop04-glow transition-[background-color,border-color] duration-200 [anchor-name:--tpop04-a] hover:border-tpop04-glow/50 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:border-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:bg-tpop04-glow/12">
<svg viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><circle cx="10" cy="10" r="7.2"/><path d="M10 9v5M10 6.4v.7"/></svg>
</button>
</div>
<p class="mt-2 mb-4.5 font-tpop04-mono text-[12.5px] leading-normal text-tpop04-mut">passed in 2m 41s · 318 tests</p>
<ol class="grid gap-2.5 border-t border-tpop04-rule pt-4">
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">1</span><span><code class="font-tpop04-mono text-tpop04-ink">starting:open:</code> holds the entry values</span></li>
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">2</span><span><code class="font-tpop04-mono text-tpop04-ink">transition-discrete</code> lets display join in</span></li>
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">3</span><span><code class="font-tpop04-mono text-tpop04-ink">overlay</code> keeps it in the top layer while it leaves</span></li>
</ol>
</div>
</div>
<div popover="auto" id="tpop-04-panel" role="group" aria-label="Build details" class="fixed m-auto w-[min(17rem,calc(100vw-32px))] rounded-2xl border border-tpop04-glow/70 bg-tpop04-surface p-4 text-tpop04-ink opacity-0 -translate-y-2 scale-[0.97] blur-xs shadow-[0_0_0_1px_oklch(0.9_0.2_125/0.12),0_24px_60px_-20px_oklch(0.9_0.2_125/0.3)] transition-[opacity,translate,scale,filter,display,overlay] duration-140 ease-in transition-discrete [position-anchor:--tpop04-a] [position-area:block-end_span-inline-start] [position-try-fallbacks:flip-block] open:translate-y-0 open:scale-100 open:opacity-100 open:blur-none open:duration-260 open:ease-[cubic-bezier(0.2,0.9,0.25,1)] starting:open:-translate-y-2 starting:open:scale-[0.97] starting:open:opacity-0 starting:open:blur-xs motion-reduce:transition-none supports-[anchor-name:--a]:inset-auto supports-[anchor-name:--a]:m-0 supports-[anchor-name:--a]:mt-2.5">
<p class="mb-3 text-[10.5px] font-bold uppercase tracking-[0.2em] text-tpop04-glow">Stage timings</p>
<ul class="grid gap-2">
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">install<span class="text-tpop04-ink tabular-nums">18s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[11%] rounded-full bg-tpop04-glow/70"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">typecheck<span class="text-tpop04-ink tabular-nums">41s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[25%] rounded-full bg-tpop04-glow/70"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">unit<span class="text-tpop04-ink tabular-nums">1m 06s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[41%] rounded-full bg-tpop04-glow"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">bundle<span class="text-tpop04-ink tabular-nums">36s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[22%] rounded-full bg-tpop04-glow/70"></span></span></li>
</ul>
<p class="mt-3.5 border-t border-tpop04-rule pt-3 text-[12px] leading-normal text-tpop04-mut">Close me: 140ms out, still on top the whole way.</p>
</div>
</section><section class="tpop-04 group/root w-full min-h-svh bg-tpop04-void bg-[radial-gradient(60%_45%_at_50%_40%,var(--color-tpop04-halo),transparent)] px-4.5 py-10 font-tpop04-sans text-tpop04-ink" aria-label="Popover entry and exit animation demo">
<div class="grid min-h-[calc(100svh-80px)] max-w-full place-items-center">
<div class="w-full min-w-0 max-w-[27rem] rounded-2xl border border-tpop04-rule bg-tpop04-surface px-4.5 pt-4.5 pb-4 shadow-[0_40px_80px_-40px_oklch(0_0_0/0.9)]">
<div class="flex min-w-0 items-center gap-2.5">
<span aria-hidden="true" class="size-2.5 flex-none rounded-full bg-tpop04-glow shadow-[0_0_12px_var(--color-tpop04-glow)]"></span>
<p class="min-w-0 flex-auto font-tpop04-mono text-[14px] font-semibold leading-tight">Build 4821 · main</p>
<span class="font-tpop04-mono text-[10.5px] uppercase tracking-[0.14em] text-tpop04-mut after:content-['closed'] group-has-[#tpop-04-panel:popover-open]/root:text-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:after:content-['open']" aria-hidden="true"></span>
<button id="tpop-04-trigger" type="button" popovertarget="tpop-04-panel" aria-label="Build details" class="inline-flex size-11 flex-none cursor-pointer items-center justify-center rounded-full border border-tpop04-rule text-tpop04-glow transition-[background-color,border-color] duration-200 [anchor-name:--tpop04-a] hover:border-tpop04-glow/50 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:border-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:bg-tpop04-glow/12">
<svg viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><circle cx="10" cy="10" r="7.2"/><path d="M10 9v5M10 6.4v.7"/></svg>
</button>
</div>
<p class="mt-2 mb-4.5 font-tpop04-mono text-[12.5px] leading-normal text-tpop04-mut">passed in 2m 41s · 318 tests</p>
<ol class="grid gap-2.5 border-t border-tpop04-rule pt-4">
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">1</span><span><code class="font-tpop04-mono text-tpop04-ink">starting:open:</code> holds the entry values</span></li>
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">2</span><span><code class="font-tpop04-mono text-tpop04-ink">transition-discrete</code> lets display join in</span></li>
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">3</span><span><code class="font-tpop04-mono text-tpop04-ink">overlay</code> keeps it in the top layer while it leaves</span></li>
</ol>
</div>
</div>
<div popover="auto" id="tpop-04-panel" role="group" aria-label="Build details" class="fixed m-auto w-[min(17rem,calc(100vw-32px))] rounded-2xl border border-tpop04-glow/70 bg-tpop04-surface p-4 text-tpop04-ink opacity-0 -translate-y-2 scale-[0.97] blur-xs shadow-[0_0_0_1px_oklch(0.9_0.2_125/0.12),0_24px_60px_-20px_oklch(0.9_0.2_125/0.3)] transition-[opacity,translate,scale,filter,display,overlay] duration-140 ease-in transition-discrete [position-anchor:--tpop04-a] [position-area:block-end_span-inline-start] [position-try-fallbacks:flip-block] open:translate-y-0 open:scale-100 open:opacity-100 open:blur-none open:duration-260 open:ease-[cubic-bezier(0.2,0.9,0.25,1)] starting:open:-translate-y-2 starting:open:scale-[0.97] starting:open:opacity-0 starting:open:blur-xs motion-reduce:transition-none supports-[anchor-name:--a]:inset-auto supports-[anchor-name:--a]:m-0 supports-[anchor-name:--a]:mt-2.5">
<p class="mb-3 text-[10.5px] font-bold uppercase tracking-[0.2em] text-tpop04-glow">Stage timings</p>
<ul class="grid gap-2">
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">install<span class="text-tpop04-ink tabular-nums">18s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[11%] rounded-full bg-tpop04-glow/70"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">typecheck<span class="text-tpop04-ink tabular-nums">41s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[25%] rounded-full bg-tpop04-glow/70"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">unit<span class="text-tpop04-ink tabular-nums">1m 06s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[41%] rounded-full bg-tpop04-glow"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">bundle<span class="text-tpop04-ink tabular-nums">36s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[22%] rounded-full bg-tpop04-glow/70"></span></span></li>
</ul>
<p class="mt-3.5 border-t border-tpop04-rule pt-3 text-[12px] leading-normal text-tpop04-mut">Close me: 140ms out, still on top the whole way.</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-tpop04-void: #030403;
--color-tpop04-void: oklch(0.1 0.005 130);
--color-tpop04-halo: #12190499;
--color-tpop04-halo: oklch(0.2 0.04 125 / 0.6);
--color-tpop04-surface: #0c0e0b;
--color-tpop04-surface: oklch(0.16 0.006 130);
--color-tpop04-ink: #f3f7f0;
--color-tpop04-ink: oklch(0.97 0.01 130);
--color-tpop04-mut: #a0a79a;
--color-tpop04-mut: oklch(0.72 0.02 130);
--color-tpop04-rule: #252722;
--color-tpop04-rule: oklch(0.27 0.01 130);
--color-tpop04-glow: #c2f83d;
--color-tpop04-glow: oklch(0.91 0.21 125);
--font-tpop04-sans: 'Geist', system-ui, sans-serif;
--font-tpop04-mono: 'Geist Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tpop-04 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tpop04-void: #030403;
--color-tpop04-void: oklch(0.1 0.005 130);
--color-tpop04-halo: #12190499;
--color-tpop04-halo: oklch(0.2 0.04 125 / 0.6);
--color-tpop04-surface: #0c0e0b;
--color-tpop04-surface: oklch(0.16 0.006 130);
--color-tpop04-ink: #f3f7f0;
--color-tpop04-ink: oklch(0.97 0.01 130);
--color-tpop04-mut: #a0a79a;
--color-tpop04-mut: oklch(0.72 0.02 130);
--color-tpop04-rule: #252722;
--color-tpop04-rule: oklch(0.27 0.01 130);
--color-tpop04-glow: #c2f83d;
--color-tpop04-glow: oklch(0.91 0.21 125);
--font-tpop04-sans: 'Geist', system-ui, sans-serif;
--font-tpop04-mono: 'Geist Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tpop-04 {
width: 100%;
min-height: 100svh;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tpop04-void: #030403;
--color-tpop04-void: oklch(0.1 0.005 130);
--color-tpop04-halo: #12190499;
--color-tpop04-halo: oklch(0.2 0.04 125 / 0.6);
--color-tpop04-surface: #0c0e0b;
--color-tpop04-surface: oklch(0.16 0.006 130);
--color-tpop04-ink: #f3f7f0;
--color-tpop04-ink: oklch(0.97 0.01 130);
--color-tpop04-mut: #a0a79a;
--color-tpop04-mut: oklch(0.72 0.02 130);
--color-tpop04-rule: #252722;
--color-tpop04-rule: oklch(0.27 0.01 130);
--color-tpop04-glow: #c2f83d;
--color-tpop04-glow: oklch(0.91 0.21 125);
--font-tpop04-sans: 'Geist', system-ui, sans-serif;
--font-tpop04-mono: 'Geist Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tpop-04 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tpop04-void: #030403;
--color-tpop04-void: oklch(0.1 0.005 130);
--color-tpop04-halo: #12190499;
--color-tpop04-halo: oklch(0.2 0.04 125 / 0.6);
--color-tpop04-surface: #0c0e0b;
--color-tpop04-surface: oklch(0.16 0.006 130);
--color-tpop04-ink: #f3f7f0;
--color-tpop04-ink: oklch(0.97 0.01 130);
--color-tpop04-mut: #a0a79a;
--color-tpop04-mut: oklch(0.72 0.02 130);
--color-tpop04-rule: #252722;
--color-tpop04-rule: oklch(0.27 0.01 130);
--color-tpop04-glow: #c2f83d;
--color-tpop04-glow: oklch(0.91 0.21 125);
--font-tpop04-sans: 'Geist', system-ui, sans-serif;
--font-tpop04-mono: 'Geist Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tpop-04 {
width: 100%;
min-height: 100svh;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..800&family=Geist+Mono:wght@400..600&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..800&family=Geist+Mono:wght@400..600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The closed rule, the
:popover-openrule, the@starting-styleblock and the reduced-motion block became one class list — and the cascade-order trap the original documented disappears, because Tailwind always emitsstarting:afteropen:. transition: … display 240ms allow-discrete, overlay 240ms allow-discretebecametransition-[…,display,overlay] transition-discrete.- UI upgrade from the CSS original: asymmetric timing (260ms eased entry, 140ms exit) via
open:duration-260, plus a softblur-xsmaterialise; the original used one duration both ways. - UI upgrade: a live
closed/openreadout generated withafter:content-[…]andgroup-has-[…:popover-open]/root:, stage timings drawn as proportional bars, and the recipe steps now name the Tailwind utilities; muted copy rose from#83887ftooklch(0.72).
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 Popover Entry and Exit Animation
Source: https://codefronts.com/snippets/tailwind-css-popovers/tailwind-popover-entry-exit-animation/
The three-part recipe almost everyone ships two-thirds of, as utilities: starting:open: for the entry values, transition-discrete so display animates, and overlay in the transition list so the panel stays in the top layer while it leaves. The port adds an asymmetric timing — 260ms in, 140ms out — with nothing more than duration-140 open:duration-260.
## HTML
```html
<section class="tpop-04 group/root w-full min-h-svh bg-tpop04-void bg-[radial-gradient(60%_45%_at_50%_40%,var(--color-tpop04-halo),transparent)] px-4.5 py-10 font-tpop04-sans text-tpop04-ink" aria-label="Popover entry and exit animation demo">
<div class="grid min-h-[calc(100svh-80px)] max-w-full place-items-center">
<div class="w-full min-w-0 max-w-[27rem] rounded-2xl border border-tpop04-rule bg-tpop04-surface px-4.5 pt-4.5 pb-4 shadow-[0_40px_80px_-40px_oklch(0_0_0/0.9)]">
<div class="flex min-w-0 items-center gap-2.5">
<span aria-hidden="true" class="size-2.5 flex-none rounded-full bg-tpop04-glow shadow-[0_0_12px_var(--color-tpop04-glow)]"></span>
<p class="min-w-0 flex-auto font-tpop04-mono text-[14px] font-semibold leading-tight">Build 4821 · main</p>
<span class="font-tpop04-mono text-[10.5px] uppercase tracking-[0.14em] text-tpop04-mut after:content-['closed'] group-has-[#tpop-04-panel:popover-open]/root:text-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:after:content-['open']" aria-hidden="true"></span>
<button id="tpop-04-trigger" type="button" popovertarget="tpop-04-panel" aria-label="Build details" class="inline-flex size-11 flex-none cursor-pointer items-center justify-center rounded-full border border-tpop04-rule text-tpop04-glow transition-[background-color,border-color] duration-200 [anchor-name:--tpop04-a] hover:border-tpop04-glow/50 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:border-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:bg-tpop04-glow/12">
<svg viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><circle cx="10" cy="10" r="7.2"/><path d="M10 9v5M10 6.4v.7"/></svg>
</button>
</div>
<p class="mt-2 mb-4.5 font-tpop04-mono text-[12.5px] leading-normal text-tpop04-mut">passed in 2m 41s · 318 tests</p>
<ol class="grid gap-2.5 border-t border-tpop04-rule pt-4">
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">1</span><span><code class="font-tpop04-mono text-tpop04-ink">starting:open:</code> holds the entry values</span></li>
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">2</span><span><code class="font-tpop04-mono text-tpop04-ink">transition-discrete</code> lets display join in</span></li>
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">3</span><span><code class="font-tpop04-mono text-tpop04-ink">overlay</code> keeps it in the top layer while it leaves</span></li>
</ol>
</div>
</div>
<div popover="auto" id="tpop-04-panel" role="group" aria-label="Build details" class="fixed m-auto w-[min(17rem,calc(100vw-32px))] rounded-2xl border border-tpop04-glow/70 bg-tpop04-surface p-4 text-tpop04-ink opacity-0 -translate-y-2 scale-[0.97] blur-xs shadow-[0_0_0_1px_oklch(0.9_0.2_125/0.12),0_24px_60px_-20px_oklch(0.9_0.2_125/0.3)] transition-[opacity,translate,scale,filter,display,overlay] duration-140 ease-in transition-discrete [position-anchor:--tpop04-a] [position-area:block-end_span-inline-start] [position-try-fallbacks:flip-block] open:translate-y-0 open:scale-100 open:opacity-100 open:blur-none open:duration-260 open:ease-[cubic-bezier(0.2,0.9,0.25,1)] starting:open:-translate-y-2 starting:open:scale-[0.97] starting:open:opacity-0 starting:open:blur-xs motion-reduce:transition-none supports-[anchor-name:--a]:inset-auto supports-[anchor-name:--a]:m-0 supports-[anchor-name:--a]:mt-2.5">
<p class="mb-3 text-[10.5px] font-bold uppercase tracking-[0.2em] text-tpop04-glow">Stage timings</p>
<ul class="grid gap-2">
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">install<span class="text-tpop04-ink tabular-nums">18s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[11%] rounded-full bg-tpop04-glow/70"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">typecheck<span class="text-tpop04-ink tabular-nums">41s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[25%] rounded-full bg-tpop04-glow/70"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">unit<span class="text-tpop04-ink tabular-nums">1m 06s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[41%] rounded-full bg-tpop04-glow"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">bundle<span class="text-tpop04-ink tabular-nums">36s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[22%] rounded-full bg-tpop04-glow/70"></span></span></li>
</ul>
<p class="mt-3.5 border-t border-tpop04-rule pt-3 text-[12px] leading-normal text-tpop04-mut">Close me: 140ms out, still on top the whole way.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpop04-void: #030403;
--color-tpop04-void: oklch(0.1 0.005 130);
--color-tpop04-halo: #12190499;
--color-tpop04-halo: oklch(0.2 0.04 125 / 0.6);
--color-tpop04-surface: #0c0e0b;
--color-tpop04-surface: oklch(0.16 0.006 130);
--color-tpop04-ink: #f3f7f0;
--color-tpop04-ink: oklch(0.97 0.01 130);
--color-tpop04-mut: #a0a79a;
--color-tpop04-mut: oklch(0.72 0.02 130);
--color-tpop04-rule: #252722;
--color-tpop04-rule: oklch(0.27 0.01 130);
--color-tpop04-glow: #c2f83d;
--color-tpop04-glow: oklch(0.91 0.21 125);
--font-tpop04-sans: 'Geist', system-ui, sans-serif;
--font-tpop04-mono: 'Geist Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tpop-04 {
width: 100%;
min-height: 100svh;
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Popover Entry and Exit Animation
Source: https://codefronts.com/snippets/tailwind-css-popovers/tailwind-popover-entry-exit-animation/
The three-part recipe almost everyone ships two-thirds of, as utilities: starting:open: for the entry values, transition-discrete so display animates, and overlay in the transition list so the panel stays in the top layer while it leaves. The port adds an asymmetric timing — 260ms in, 140ms out — with nothing more than duration-140 open:duration-260.
## HTML
```html
<section class="tpop-04 group/root w-full min-h-svh bg-tpop04-void bg-[radial-gradient(60%_45%_at_50%_40%,var(--color-tpop04-halo),transparent)] px-4.5 py-10 font-tpop04-sans text-tpop04-ink" aria-label="Popover entry and exit animation demo">
<div class="grid min-h-[calc(100svh-80px)] max-w-full place-items-center">
<div class="w-full min-w-0 max-w-[27rem] rounded-2xl border border-tpop04-rule bg-tpop04-surface px-4.5 pt-4.5 pb-4 shadow-[0_40px_80px_-40px_oklch(0_0_0/0.9)]">
<div class="flex min-w-0 items-center gap-2.5">
<span aria-hidden="true" class="size-2.5 flex-none rounded-full bg-tpop04-glow shadow-[0_0_12px_var(--color-tpop04-glow)]"></span>
<p class="min-w-0 flex-auto font-tpop04-mono text-[14px] font-semibold leading-tight">Build 4821 · main</p>
<span class="font-tpop04-mono text-[10.5px] uppercase tracking-[0.14em] text-tpop04-mut after:content-['closed'] group-has-[#tpop-04-panel:popover-open]/root:text-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:after:content-['open']" aria-hidden="true"></span>
<button id="tpop-04-trigger" type="button" popovertarget="tpop-04-panel" aria-label="Build details" class="inline-flex size-11 flex-none cursor-pointer items-center justify-center rounded-full border border-tpop04-rule text-tpop04-glow transition-[background-color,border-color] duration-200 [anchor-name:--tpop04-a] hover:border-tpop04-glow/50 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:border-tpop04-glow group-has-[#tpop-04-panel:popover-open]/root:bg-tpop04-glow/12">
<svg viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><circle cx="10" cy="10" r="7.2"/><path d="M10 9v5M10 6.4v.7"/></svg>
</button>
</div>
<p class="mt-2 mb-4.5 font-tpop04-mono text-[12.5px] leading-normal text-tpop04-mut">passed in 2m 41s · 318 tests</p>
<ol class="grid gap-2.5 border-t border-tpop04-rule pt-4">
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">1</span><span><code class="font-tpop04-mono text-tpop04-ink">starting:open:</code> holds the entry values</span></li>
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">2</span><span><code class="font-tpop04-mono text-tpop04-ink">transition-discrete</code> lets display join in</span></li>
<li class="flex min-w-0 items-start gap-2.5 text-[13px] leading-normal text-tpop04-mut"><span class="grid size-5 flex-none place-items-center rounded-md bg-tpop04-glow font-tpop04-mono text-[11px] font-bold text-tpop04-void">3</span><span><code class="font-tpop04-mono text-tpop04-ink">overlay</code> keeps it in the top layer while it leaves</span></li>
</ol>
</div>
</div>
<div popover="auto" id="tpop-04-panel" role="group" aria-label="Build details" class="fixed m-auto w-[min(17rem,calc(100vw-32px))] rounded-2xl border border-tpop04-glow/70 bg-tpop04-surface p-4 text-tpop04-ink opacity-0 -translate-y-2 scale-[0.97] blur-xs shadow-[0_0_0_1px_oklch(0.9_0.2_125/0.12),0_24px_60px_-20px_oklch(0.9_0.2_125/0.3)] transition-[opacity,translate,scale,filter,display,overlay] duration-140 ease-in transition-discrete [position-anchor:--tpop04-a] [position-area:block-end_span-inline-start] [position-try-fallbacks:flip-block] open:translate-y-0 open:scale-100 open:opacity-100 open:blur-none open:duration-260 open:ease-[cubic-bezier(0.2,0.9,0.25,1)] starting:open:-translate-y-2 starting:open:scale-[0.97] starting:open:opacity-0 starting:open:blur-xs motion-reduce:transition-none supports-[anchor-name:--a]:inset-auto supports-[anchor-name:--a]:m-0 supports-[anchor-name:--a]:mt-2.5">
<p class="mb-3 text-[10.5px] font-bold uppercase tracking-[0.2em] text-tpop04-glow">Stage timings</p>
<ul class="grid gap-2">
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">install<span class="text-tpop04-ink tabular-nums">18s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[11%] rounded-full bg-tpop04-glow/70"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">typecheck<span class="text-tpop04-ink tabular-nums">41s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[25%] rounded-full bg-tpop04-glow/70"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">unit<span class="text-tpop04-ink tabular-nums">1m 06s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[41%] rounded-full bg-tpop04-glow"></span></span></li>
<li class="grid min-w-0 grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 font-tpop04-mono text-[12.5px] text-tpop04-mut">bundle<span class="text-tpop04-ink tabular-nums">36s</span><span class="col-span-2 h-1 overflow-hidden rounded-full bg-tpop04-rule"><span class="block h-full w-[22%] rounded-full bg-tpop04-glow/70"></span></span></li>
</ul>
<p class="mt-3.5 border-t border-tpop04-rule pt-3 text-[12px] leading-normal text-tpop04-mut">Close me: 140ms out, still on top the whole way.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpop04-void: #030403;
--color-tpop04-void: oklch(0.1 0.005 130);
--color-tpop04-halo: #12190499;
--color-tpop04-halo: oklch(0.2 0.04 125 / 0.6);
--color-tpop04-surface: #0c0e0b;
--color-tpop04-surface: oklch(0.16 0.006 130);
--color-tpop04-ink: #f3f7f0;
--color-tpop04-ink: oklch(0.97 0.01 130);
--color-tpop04-mut: #a0a79a;
--color-tpop04-mut: oklch(0.72 0.02 130);
--color-tpop04-rule: #252722;
--color-tpop04-rule: oklch(0.27 0.01 130);
--color-tpop04-glow: #c2f83d;
--color-tpop04-glow: oklch(0.91 0.21 125);
--font-tpop04-sans: 'Geist', system-ui, sans-serif;
--font-tpop04-mono: 'Geist Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tpop-04 {
width: 100%;
min-height: 100svh;
}
```How this works
Closed values bare, open values behind open:, pre-open values behind starting:open:.
class="opacity-0 -translate-y-2 scale-[0.97] blur-xs
transition-[opacity,translate,scale,filter,display,overlay]
transition-discrete duration-140 ease-in
open:opacity-100 open:translate-y-0 open:scale-100 open:blur-none
open:duration-260 open:ease-[cubic-bezier(0.2,0.9,0.25,1)]
starting:open:opacity-0 starting:open:-translate-y-2 …"Overlay is top-layer membership as a property. Leave it out and the browser removes the panel from the top layer the instant it closes — the exit fade then plays behind the page. With it, removal waits for the transition.
Cascade order is solved for you. The CSS version had to place @starting-style after the :popover-open rule or lose the entry; Tailwind emits variant rules in a fixed order, so starting:open: always wins over open:.
Asymmetric timing is free. A transition uses the duration of the state it is heading into: open:duration-260 times the entry, bare duration-140 times the exit, so the panel arrives gracefully and gets out of the way fast.
Make it yours
- Pop instead of slide: drop the translate utilities and use
scale-90/open:scale-100. - Remove the blur for pure compositor work: delete
blur-xs,open:blur-noneandfilterfrom the list. - Spring feel:
open:ease-[cubic-bezier(0.34,1.56,0.64,1)]overshoots slightly on entry.
Gotchas — read before shipping
- Omitting
overlayfrom the list is the classic failure — the exit flashes behind the page. transition-discretewithoutdisplayin the list does nothing, and vice versa.- Animating
insetorheighton an anchored popover fights position resolution; stick to transform, opacity and filter.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 125+ | 26+ | not yet | 125+ |
Floor set by oklch(), anchor positioning as this demo is written. The core technique itself goes back further — Chrome 117+.
@starting-style and allow-discrete are Chrome 117 / Safari 17.5 / Firefox 129. Without them the panel opens and closes instantly — the reduced-motion presentation — and still works. Anchoring (Chrome 125 / Safari 26 / Firefox 131) is an enhancement here.