15 / 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 Filter Dropdown with Checkboxes
The clearest real use of popover="manual": a filter menu you tick several boxes in, which must not vanish because a click landed just outside it. The port makes the panel live with zero JS — a CSS counter ([counter-reset:tpop15] + checked:[counter-increment:tpop15]) prints how many filters are ticked, the chip shows a dot whenever any are, and a native type="reset" restores the defaults.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tpop-15 transition-[--tpop15-d] duration-300 ease-in-out motion-reduce:transition-none dark:[--tpop15-d:1] [[data-theme=dark]_&]:[--tpop15-d:1] [[data-theme=light]_&]:[--tpop15-d:0] group/root w-full min-h-svh bg-tpop15-bg px-4 py-9.5 font-tpop15-sans text-tpop15-ink" aria-label="Filter dropdown with checkboxes demo">
<div class="grid min-h-[calc(100svh-76px)] max-w-full place-items-center">
<div class="w-full min-w-0 max-w-[33rem] border-2 border-tpop15-ink bg-tpop15-surface shadow-[7px_7px_0_var(--color-tpop15-ember)]">
<div class="flex flex-wrap items-center justify-between gap-2.5 border-b-2 border-tpop15-ink px-3 py-2.5">
<span class="min-w-0 font-tpop15-mono text-[13px] font-semibold leading-tight">Shipments · 1,284 rows</span>
<button id="tpop-15-trigger" type="button" popovertarget="tpop-15-filters" popovertargetaction="show" class="relative inline-flex min-h-11 cursor-pointer items-center gap-2 border-2 border-tpop15-ink bg-tpop15-ink px-3 text-[12px] font-bold uppercase tracking-[0.05em] text-tpop15-surface transition-[translate,box-shadow] duration-100 [anchor-name:--tpop15-a] hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-[3px_3px_0_var(--color-tpop15-ember)] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember motion-reduce:transition-none">
<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M2.5 4h11M4.5 8h7M6.5 12h3"/></svg>
Filters
<span aria-hidden="true" class="absolute -top-1.5 -right-1.5 hidden size-3 border-2 border-tpop15-surface bg-tpop15-ember group-has-[#tpop-15-filters_input:checked]/root:block"></span>
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Ref</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Lane</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Status</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-right text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Weight</th></tr>
</thead>
<tbody class="font-tpop15-mono text-[12.5px]">
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8841</td><td class="px-3 py-2.5 whitespace-nowrap">Rotterdam → Leeds</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink/8 px-1.5 py-0.5 text-[11px]">transit</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">1,240 kg</td></tr>
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8842</td><td class="px-3 py-2.5 whitespace-nowrap">Gdansk → Bremen</td><td class="px-3 py-2.5"><span class="bg-tpop15-ember/15 px-1.5 py-0.5 text-[11px] text-tpop15-emberdeep">customs</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">860 kg</td></tr>
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8843</td><td class="px-3 py-2.5 whitespace-nowrap">Vigo → Nantes</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink/8 px-1.5 py-0.5 text-[11px]">transit</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">3,105 kg</td></tr>
<tr><td class="px-3 py-2.5">SH-8844</td><td class="px-3 py-2.5 whitespace-nowrap">Oslo → Aarhus</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink px-1.5 py-0.5 text-[11px] text-tpop15-surface">exception</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">412 kg</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div popover="manual" id="tpop-15-filters" role="group" aria-labelledby="tpop-15-legend" class="fixed m-auto w-[min(18rem,calc(100vw-24px))] border-2 border-tpop15-ink bg-tpop15-surface p-3.5 text-tpop15-ink opacity-0 shadow-[7px_7px_0_var(--color-tpop15-ember)] transition-[opacity,translate,display,overlay] duration-150 ease-out transition-discrete [position-anchor:--tpop15-a] [position-area:block-end_span-inline-start] [position-try-fallbacks:flip-block,flip-inline] open:opacity-100 starting:open:-translate-y-1.5 starting:open:opacity-0 motion-reduce:transition-none supports-[anchor-name:--a]:inset-auto supports-[anchor-name:--a]:m-0 supports-[anchor-name:--a]:mt-2.5">
<div class="mb-1.5 flex items-center justify-between gap-2">
<p id="tpop-15-legend" class="text-[11.5px] font-bold uppercase tracking-[0.16em]">Filter shipments</p>
<button type="button" popovertarget="tpop-15-filters" popovertargetaction="hide" aria-label="Close filters" class="-my-3 -mr-3 inline-flex size-11 cursor-pointer items-center justify-center hover:text-tpop15-emberdeep focus-visible:outline-3 focus-visible:-outline-offset-3 focus-visible:outline-tpop15-ember">
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>
</button>
</div>
<form class="[counter-reset:tpop15]">
<fieldset class="grid">
<legend class="mb-1 font-tpop15-mono text-[11px] font-semibold uppercase tracking-[0.1em] text-tpop15-mut">Status</legend>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> In transit</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Held at customs</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Delivered</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Exception raised</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Cancelled</label>
</fieldset>
<p class="mt-2 mb-3 flex items-center justify-between gap-2 border-t border-tpop15-hair pt-2.5 text-[12.5px] font-semibold">
<span><span class="font-tpop15-mono text-tpop15-emberdeep after:content-[counter(tpop15)]"></span> selected · click the table, I stay</span>
<button type="reset" class="min-h-11 cursor-pointer px-1 text-[12px] font-bold uppercase tracking-[0.05em] text-tpop15-mut underline underline-offset-4 hover:text-tpop15-ink focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember">Reset</button>
</p>
<div class="flex flex-wrap gap-2">
<button type="button" popovertarget="tpop-15-filters" popovertargetaction="hide" class="min-h-11 min-w-0 flex-auto cursor-pointer border-2 border-tpop15-ink bg-tpop15-ink px-3 text-[12px] font-bold uppercase tracking-[0.06em] text-tpop15-surface transition-colors hover:bg-tpop15-ember hover:border-tpop15-ember hover:text-tpop15-ink focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember">Apply filters</button>
</div>
</form>
</div>
</section><section class="tpop-15 transition-[--tpop15-d] duration-300 ease-in-out motion-reduce:transition-none dark:[--tpop15-d:1] [[data-theme=dark]_&]:[--tpop15-d:1] [[data-theme=light]_&]:[--tpop15-d:0] group/root w-full min-h-svh bg-tpop15-bg px-4 py-9.5 font-tpop15-sans text-tpop15-ink" aria-label="Filter dropdown with checkboxes demo">
<div class="grid min-h-[calc(100svh-76px)] max-w-full place-items-center">
<div class="w-full min-w-0 max-w-[33rem] border-2 border-tpop15-ink bg-tpop15-surface shadow-[7px_7px_0_var(--color-tpop15-ember)]">
<div class="flex flex-wrap items-center justify-between gap-2.5 border-b-2 border-tpop15-ink px-3 py-2.5">
<span class="min-w-0 font-tpop15-mono text-[13px] font-semibold leading-tight">Shipments · 1,284 rows</span>
<button id="tpop-15-trigger" type="button" popovertarget="tpop-15-filters" popovertargetaction="show" class="relative inline-flex min-h-11 cursor-pointer items-center gap-2 border-2 border-tpop15-ink bg-tpop15-ink px-3 text-[12px] font-bold uppercase tracking-[0.05em] text-tpop15-surface transition-[translate,box-shadow] duration-100 [anchor-name:--tpop15-a] hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-[3px_3px_0_var(--color-tpop15-ember)] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember motion-reduce:transition-none">
<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M2.5 4h11M4.5 8h7M6.5 12h3"/></svg>
Filters
<span aria-hidden="true" class="absolute -top-1.5 -right-1.5 hidden size-3 border-2 border-tpop15-surface bg-tpop15-ember group-has-[#tpop-15-filters_input:checked]/root:block"></span>
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Ref</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Lane</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Status</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-right text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Weight</th></tr>
</thead>
<tbody class="font-tpop15-mono text-[12.5px]">
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8841</td><td class="px-3 py-2.5 whitespace-nowrap">Rotterdam → Leeds</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink/8 px-1.5 py-0.5 text-[11px]">transit</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">1,240 kg</td></tr>
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8842</td><td class="px-3 py-2.5 whitespace-nowrap">Gdansk → Bremen</td><td class="px-3 py-2.5"><span class="bg-tpop15-ember/15 px-1.5 py-0.5 text-[11px] text-tpop15-emberdeep">customs</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">860 kg</td></tr>
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8843</td><td class="px-3 py-2.5 whitespace-nowrap">Vigo → Nantes</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink/8 px-1.5 py-0.5 text-[11px]">transit</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">3,105 kg</td></tr>
<tr><td class="px-3 py-2.5">SH-8844</td><td class="px-3 py-2.5 whitespace-nowrap">Oslo → Aarhus</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink px-1.5 py-0.5 text-[11px] text-tpop15-surface">exception</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">412 kg</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div popover="manual" id="tpop-15-filters" role="group" aria-labelledby="tpop-15-legend" class="fixed m-auto w-[min(18rem,calc(100vw-24px))] border-2 border-tpop15-ink bg-tpop15-surface p-3.5 text-tpop15-ink opacity-0 shadow-[7px_7px_0_var(--color-tpop15-ember)] transition-[opacity,translate,display,overlay] duration-150 ease-out transition-discrete [position-anchor:--tpop15-a] [position-area:block-end_span-inline-start] [position-try-fallbacks:flip-block,flip-inline] open:opacity-100 starting:open:-translate-y-1.5 starting:open:opacity-0 motion-reduce:transition-none supports-[anchor-name:--a]:inset-auto supports-[anchor-name:--a]:m-0 supports-[anchor-name:--a]:mt-2.5">
<div class="mb-1.5 flex items-center justify-between gap-2">
<p id="tpop-15-legend" class="text-[11.5px] font-bold uppercase tracking-[0.16em]">Filter shipments</p>
<button type="button" popovertarget="tpop-15-filters" popovertargetaction="hide" aria-label="Close filters" class="-my-3 -mr-3 inline-flex size-11 cursor-pointer items-center justify-center hover:text-tpop15-emberdeep focus-visible:outline-3 focus-visible:-outline-offset-3 focus-visible:outline-tpop15-ember">
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>
</button>
</div>
<form class="[counter-reset:tpop15]">
<fieldset class="grid">
<legend class="mb-1 font-tpop15-mono text-[11px] font-semibold uppercase tracking-[0.1em] text-tpop15-mut">Status</legend>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> In transit</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Held at customs</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Delivered</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Exception raised</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Cancelled</label>
</fieldset>
<p class="mt-2 mb-3 flex items-center justify-between gap-2 border-t border-tpop15-hair pt-2.5 text-[12.5px] font-semibold">
<span><span class="font-tpop15-mono text-tpop15-emberdeep after:content-[counter(tpop15)]"></span> selected · click the table, I stay</span>
<button type="reset" class="min-h-11 cursor-pointer px-1 text-[12px] font-bold uppercase tracking-[0.05em] text-tpop15-mut underline underline-offset-4 hover:text-tpop15-ink focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember">Reset</button>
</p>
<div class="flex flex-wrap gap-2">
<button type="button" popovertarget="tpop-15-filters" popovertargetaction="hide" class="min-h-11 min-w-0 flex-auto cursor-pointer border-2 border-tpop15-ink bg-tpop15-ink px-3 text-[12px] font-bold uppercase tracking-[0.06em] text-tpop15-surface transition-colors hover:bg-tpop15-ember hover:border-tpop15-ember hover:text-tpop15-ink focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember">Apply filters</button>
</div>
</form>
</div>
</section>@import "tailwindcss";
@theme {
--color-tpop15-bg: #f2f2ef;
--color-tpop15-bg: oklch(0.96 0.003 95);
--color-tpop15-surface: #ffffff;
--color-tpop15-surface: oklch(1 0 0);
--color-tpop15-ink: #181614;
--color-tpop15-ink: oklch(0.2 0.005 70);
--color-tpop15-mut: #5e5a55;
--color-tpop15-mut: oklch(0.47 0.01 75);
--color-tpop15-hair: #dfdeda;
--color-tpop15-hair: oklch(0.9 0.006 95);
--color-tpop15-ember: #e04f1a;
--color-tpop15-ember: oklch(0.62 0.19 38);
--color-tpop15-emberdeep: #af2f02;
--color-tpop15-emberdeep: oklch(0.5 0.17 36);
--font-tpop15-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpop15-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Theme dial: 0 = light, 1 = dark. Registered so the root can transition it. */
@property --tpop15-d {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
/* 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 — plus the dark palette. Each @theme colour is
re-declared on the root as a light/dark mix of the dial, so every utility (and every open
popover, which inherits from the root) follows it. Light value last, dark value first. */
.tpop-15 {
width: 100%;
min-height: 100svh;
--color-tpop15-bg: color-mix(in oklch, oklch(0.16 0.004 70) calc(var(--tpop15-d) * 100%), oklch(0.96 0.003 95));
--color-tpop15-surface: color-mix(in oklch, oklch(0.205 0.005 70) calc(var(--tpop15-d) * 100%), oklch(1 0 0));
--color-tpop15-ink: color-mix(in oklch, oklch(0.95 0.006 90) calc(var(--tpop15-d) * 100%), oklch(0.2 0.005 70));
--color-tpop15-mut: color-mix(in oklch, oklch(0.73 0.01 80) calc(var(--tpop15-d) * 100%), oklch(0.47 0.01 75));
--color-tpop15-hair: color-mix(in oklch, oklch(0.3 0.006 70) calc(var(--tpop15-d) * 100%), oklch(0.9 0.006 95));
--color-tpop15-ember: color-mix(in oklch, oklch(0.72 0.17 40) calc(var(--tpop15-d) * 100%), oklch(0.62 0.19 38));
--color-tpop15-emberdeep: color-mix(in oklch, oklch(0.78 0.15 42) calc(var(--tpop15-d) * 100%), oklch(0.5 0.17 36));
}@import "tailwindcss";
@theme {
--color-tpop15-bg: #f2f2ef;
--color-tpop15-bg: oklch(0.96 0.003 95);
--color-tpop15-surface: #ffffff;
--color-tpop15-surface: oklch(1 0 0);
--color-tpop15-ink: #181614;
--color-tpop15-ink: oklch(0.2 0.005 70);
--color-tpop15-mut: #5e5a55;
--color-tpop15-mut: oklch(0.47 0.01 75);
--color-tpop15-hair: #dfdeda;
--color-tpop15-hair: oklch(0.9 0.006 95);
--color-tpop15-ember: #e04f1a;
--color-tpop15-ember: oklch(0.62 0.19 38);
--color-tpop15-emberdeep: #af2f02;
--color-tpop15-emberdeep: oklch(0.5 0.17 36);
--font-tpop15-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpop15-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Theme dial: 0 = light, 1 = dark. Registered so the root can transition it. */
@property --tpop15-d {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
/* 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 — plus the dark palette. Each @theme colour is
re-declared on the root as a light/dark mix of the dial, so every utility (and every open
popover, which inherits from the root) follows it. Light value last, dark value first. */
.tpop-15 {
width: 100%;
min-height: 100svh;
--color-tpop15-bg: color-mix(in oklch, oklch(0.16 0.004 70) calc(var(--tpop15-d) * 100%), oklch(0.96 0.003 95));
--color-tpop15-surface: color-mix(in oklch, oklch(0.205 0.005 70) calc(var(--tpop15-d) * 100%), oklch(1 0 0));
--color-tpop15-ink: color-mix(in oklch, oklch(0.95 0.006 90) calc(var(--tpop15-d) * 100%), oklch(0.2 0.005 70));
--color-tpop15-mut: color-mix(in oklch, oklch(0.73 0.01 80) calc(var(--tpop15-d) * 100%), oklch(0.47 0.01 75));
--color-tpop15-hair: color-mix(in oklch, oklch(0.3 0.006 70) calc(var(--tpop15-d) * 100%), oklch(0.9 0.006 95));
--color-tpop15-ember: color-mix(in oklch, oklch(0.72 0.17 40) calc(var(--tpop15-d) * 100%), oklch(0.62 0.19 38));
--color-tpop15-emberdeep: color-mix(in oklch, oklch(0.78 0.15 42) calc(var(--tpop15-d) * 100%), oklch(0.5 0.17 36));
}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-tpop15-bg: #f2f2ef;
--color-tpop15-bg: oklch(0.96 0.003 95);
--color-tpop15-surface: #ffffff;
--color-tpop15-surface: oklch(1 0 0);
--color-tpop15-ink: #181614;
--color-tpop15-ink: oklch(0.2 0.005 70);
--color-tpop15-mut: #5e5a55;
--color-tpop15-mut: oklch(0.47 0.01 75);
--color-tpop15-hair: #dfdeda;
--color-tpop15-hair: oklch(0.9 0.006 95);
--color-tpop15-ember: #e04f1a;
--color-tpop15-ember: oklch(0.62 0.19 38);
--color-tpop15-emberdeep: #af2f02;
--color-tpop15-emberdeep: oklch(0.5 0.17 36);
--font-tpop15-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpop15-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Theme dial: 0 = light, 1 = dark. Registered so the root can transition it. */
@property --tpop15-d {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
/* 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 — plus the dark palette. Each @theme colour is
re-declared on the root as a light/dark mix of the dial, so every utility (and every open
popover, which inherits from the root) follows it. Light value last, dark value first. */
.tpop-15 {
width: 100%;
min-height: 100svh;
--color-tpop15-bg: color-mix(in oklch, oklch(0.16 0.004 70) calc(var(--tpop15-d) * 100%), oklch(0.96 0.003 95));
--color-tpop15-surface: color-mix(in oklch, oklch(0.205 0.005 70) calc(var(--tpop15-d) * 100%), oklch(1 0 0));
--color-tpop15-ink: color-mix(in oklch, oklch(0.95 0.006 90) calc(var(--tpop15-d) * 100%), oklch(0.2 0.005 70));
--color-tpop15-mut: color-mix(in oklch, oklch(0.73 0.01 80) calc(var(--tpop15-d) * 100%), oklch(0.47 0.01 75));
--color-tpop15-hair: color-mix(in oklch, oklch(0.3 0.006 70) calc(var(--tpop15-d) * 100%), oklch(0.9 0.006 95));
--color-tpop15-ember: color-mix(in oklch, oklch(0.72 0.17 40) calc(var(--tpop15-d) * 100%), oklch(0.62 0.19 38));
--color-tpop15-emberdeep: color-mix(in oklch, oklch(0.78 0.15 42) calc(var(--tpop15-d) * 100%), oklch(0.5 0.17 36));
}@import "tailwindcss";
@theme {
--color-tpop15-bg: #f2f2ef;
--color-tpop15-bg: oklch(0.96 0.003 95);
--color-tpop15-surface: #ffffff;
--color-tpop15-surface: oklch(1 0 0);
--color-tpop15-ink: #181614;
--color-tpop15-ink: oklch(0.2 0.005 70);
--color-tpop15-mut: #5e5a55;
--color-tpop15-mut: oklch(0.47 0.01 75);
--color-tpop15-hair: #dfdeda;
--color-tpop15-hair: oklch(0.9 0.006 95);
--color-tpop15-ember: #e04f1a;
--color-tpop15-ember: oklch(0.62 0.19 38);
--color-tpop15-emberdeep: #af2f02;
--color-tpop15-emberdeep: oklch(0.5 0.17 36);
--font-tpop15-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpop15-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Theme dial: 0 = light, 1 = dark. Registered so the root can transition it. */
@property --tpop15-d {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
/* 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 — plus the dark palette. Each @theme colour is
re-declared on the root as a light/dark mix of the dial, so every utility (and every open
popover, which inherits from the root) follows it. Light value last, dark value first. */
.tpop-15 {
width: 100%;
min-height: 100svh;
--color-tpop15-bg: color-mix(in oklch, oklch(0.16 0.004 70) calc(var(--tpop15-d) * 100%), oklch(0.96 0.003 95));
--color-tpop15-surface: color-mix(in oklch, oklch(0.205 0.005 70) calc(var(--tpop15-d) * 100%), oklch(1 0 0));
--color-tpop15-ink: color-mix(in oklch, oklch(0.95 0.006 90) calc(var(--tpop15-d) * 100%), oklch(0.2 0.005 70));
--color-tpop15-mut: color-mix(in oklch, oklch(0.73 0.01 80) calc(var(--tpop15-d) * 100%), oklch(0.47 0.01 75));
--color-tpop15-hair: color-mix(in oklch, oklch(0.3 0.006 70) calc(var(--tpop15-d) * 100%), oklch(0.9 0.006 95));
--color-tpop15-ember: color-mix(in oklch, oklch(0.72 0.17 40) calc(var(--tpop15-d) * 100%), oklch(0.62 0.19 38));
--color-tpop15-emberdeep: color-mix(in oklch, oklch(0.78 0.15 42) calc(var(--tpop15-d) * 100%), oklch(0.5 0.17 36));
}<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..700&family=JetBrains+Mono: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..700&family=JetBrains+Mono:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
accent-color: var(--ember)becameaccent-tpop15-ember— theme colours mint accent utilities too.- Both brutalist offset shadows read the same token:
shadow-[7px_7px_0_var(--color-tpop15-ember)]. - UI upgrade from the CSS original: the chip's hard-coded "3" (which went stale the moment you unticked a box) became a truthful
:has()dot, and the panel prints a live CSS-counter total — both zero JS. - UI upgrade: a native
type="reset"button restores the defaults without script; the table gained a Status column so the filters mean something; the chip lifts on hover; the redundant Close button was removed in favour of ✕ plus Apply; muted copy rose from#6d6a63tooklch(0.47). - UI upgrade — dark mode: the stage now follows
prefers-color-scheme(and adata-themeoverride on any ancestor) through one registered dial,--tpop15-d. The root rule re-declares each@themecolour ascolor-mix(dark calc(var(--tpop15-d)*100%), light), the root sets the dial withdark:[--tpop15-d:1], andtransition-[--tpop15-d]crossfades page and open popovers together — the markup's colour utilities are unchanged.
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 Filter Dropdown with Checkboxes
Source: https://codefronts.com/snippets/tailwind-css-popovers/tailwind-filter-dropdown-checkboxes/
The clearest real use of popover="manual": a filter menu you tick several boxes in, which must not vanish because a click landed just outside it. The port makes the panel live with zero JS — a CSS counter ([counter-reset:tpop15] + checked:[counter-increment:tpop15]) prints how many filters are ticked, the chip shows a dot whenever any are, and a native type="reset" restores the defaults.
## HTML
```html
<section class="tpop-15 transition-[--tpop15-d] duration-300 ease-in-out motion-reduce:transition-none dark:[--tpop15-d:1] [[data-theme=dark]_&]:[--tpop15-d:1] [[data-theme=light]_&]:[--tpop15-d:0] group/root w-full min-h-svh bg-tpop15-bg px-4 py-9.5 font-tpop15-sans text-tpop15-ink" aria-label="Filter dropdown with checkboxes demo">
<div class="grid min-h-[calc(100svh-76px)] max-w-full place-items-center">
<div class="w-full min-w-0 max-w-[33rem] border-2 border-tpop15-ink bg-tpop15-surface shadow-[7px_7px_0_var(--color-tpop15-ember)]">
<div class="flex flex-wrap items-center justify-between gap-2.5 border-b-2 border-tpop15-ink px-3 py-2.5">
<span class="min-w-0 font-tpop15-mono text-[13px] font-semibold leading-tight">Shipments · 1,284 rows</span>
<button id="tpop-15-trigger" type="button" popovertarget="tpop-15-filters" popovertargetaction="show" class="relative inline-flex min-h-11 cursor-pointer items-center gap-2 border-2 border-tpop15-ink bg-tpop15-ink px-3 text-[12px] font-bold uppercase tracking-[0.05em] text-tpop15-surface transition-[translate,box-shadow] duration-100 [anchor-name:--tpop15-a] hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-[3px_3px_0_var(--color-tpop15-ember)] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember motion-reduce:transition-none">
<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M2.5 4h11M4.5 8h7M6.5 12h3"/></svg>
Filters
<span aria-hidden="true" class="absolute -top-1.5 -right-1.5 hidden size-3 border-2 border-tpop15-surface bg-tpop15-ember group-has-[#tpop-15-filters_input:checked]/root:block"></span>
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Ref</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Lane</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Status</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-right text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Weight</th></tr>
</thead>
<tbody class="font-tpop15-mono text-[12.5px]">
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8841</td><td class="px-3 py-2.5 whitespace-nowrap">Rotterdam → Leeds</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink/8 px-1.5 py-0.5 text-[11px]">transit</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">1,240 kg</td></tr>
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8842</td><td class="px-3 py-2.5 whitespace-nowrap">Gdansk → Bremen</td><td class="px-3 py-2.5"><span class="bg-tpop15-ember/15 px-1.5 py-0.5 text-[11px] text-tpop15-emberdeep">customs</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">860 kg</td></tr>
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8843</td><td class="px-3 py-2.5 whitespace-nowrap">Vigo → Nantes</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink/8 px-1.5 py-0.5 text-[11px]">transit</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">3,105 kg</td></tr>
<tr><td class="px-3 py-2.5">SH-8844</td><td class="px-3 py-2.5 whitespace-nowrap">Oslo → Aarhus</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink px-1.5 py-0.5 text-[11px] text-tpop15-surface">exception</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">412 kg</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div popover="manual" id="tpop-15-filters" role="group" aria-labelledby="tpop-15-legend" class="fixed m-auto w-[min(18rem,calc(100vw-24px))] border-2 border-tpop15-ink bg-tpop15-surface p-3.5 text-tpop15-ink opacity-0 shadow-[7px_7px_0_var(--color-tpop15-ember)] transition-[opacity,translate,display,overlay] duration-150 ease-out transition-discrete [position-anchor:--tpop15-a] [position-area:block-end_span-inline-start] [position-try-fallbacks:flip-block,flip-inline] open:opacity-100 starting:open:-translate-y-1.5 starting:open:opacity-0 motion-reduce:transition-none supports-[anchor-name:--a]:inset-auto supports-[anchor-name:--a]:m-0 supports-[anchor-name:--a]:mt-2.5">
<div class="mb-1.5 flex items-center justify-between gap-2">
<p id="tpop-15-legend" class="text-[11.5px] font-bold uppercase tracking-[0.16em]">Filter shipments</p>
<button type="button" popovertarget="tpop-15-filters" popovertargetaction="hide" aria-label="Close filters" class="-my-3 -mr-3 inline-flex size-11 cursor-pointer items-center justify-center hover:text-tpop15-emberdeep focus-visible:outline-3 focus-visible:-outline-offset-3 focus-visible:outline-tpop15-ember">
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>
</button>
</div>
<form class="[counter-reset:tpop15]">
<fieldset class="grid">
<legend class="mb-1 font-tpop15-mono text-[11px] font-semibold uppercase tracking-[0.1em] text-tpop15-mut">Status</legend>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> In transit</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Held at customs</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Delivered</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Exception raised</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Cancelled</label>
</fieldset>
<p class="mt-2 mb-3 flex items-center justify-between gap-2 border-t border-tpop15-hair pt-2.5 text-[12.5px] font-semibold">
<span><span class="font-tpop15-mono text-tpop15-emberdeep after:content-[counter(tpop15)]"></span> selected · click the table, I stay</span>
<button type="reset" class="min-h-11 cursor-pointer px-1 text-[12px] font-bold uppercase tracking-[0.05em] text-tpop15-mut underline underline-offset-4 hover:text-tpop15-ink focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember">Reset</button>
</p>
<div class="flex flex-wrap gap-2">
<button type="button" popovertarget="tpop-15-filters" popovertargetaction="hide" class="min-h-11 min-w-0 flex-auto cursor-pointer border-2 border-tpop15-ink bg-tpop15-ink px-3 text-[12px] font-bold uppercase tracking-[0.06em] text-tpop15-surface transition-colors hover:bg-tpop15-ember hover:border-tpop15-ember hover:text-tpop15-ink focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember">Apply filters</button>
</div>
</form>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpop15-bg: #f2f2ef;
--color-tpop15-bg: oklch(0.96 0.003 95);
--color-tpop15-surface: #ffffff;
--color-tpop15-surface: oklch(1 0 0);
--color-tpop15-ink: #181614;
--color-tpop15-ink: oklch(0.2 0.005 70);
--color-tpop15-mut: #5e5a55;
--color-tpop15-mut: oklch(0.47 0.01 75);
--color-tpop15-hair: #dfdeda;
--color-tpop15-hair: oklch(0.9 0.006 95);
--color-tpop15-ember: #e04f1a;
--color-tpop15-ember: oklch(0.62 0.19 38);
--color-tpop15-emberdeep: #af2f02;
--color-tpop15-emberdeep: oklch(0.5 0.17 36);
--font-tpop15-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpop15-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Theme dial: 0 = light, 1 = dark. Registered so the root can transition it. */
@property --tpop15-d {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
/* 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 — plus the dark palette. Each @theme colour is
re-declared on the root as a light/dark mix of the dial, so every utility (and every open
popover, which inherits from the root) follows it. Light value last, dark value first. */
.tpop-15 {
width: 100%;
min-height: 100svh;
--color-tpop15-bg: color-mix(in oklch, oklch(0.16 0.004 70) calc(var(--tpop15-d) * 100%), oklch(0.96 0.003 95));
--color-tpop15-surface: color-mix(in oklch, oklch(0.205 0.005 70) calc(var(--tpop15-d) * 100%), oklch(1 0 0));
--color-tpop15-ink: color-mix(in oklch, oklch(0.95 0.006 90) calc(var(--tpop15-d) * 100%), oklch(0.2 0.005 70));
--color-tpop15-mut: color-mix(in oklch, oklch(0.73 0.01 80) calc(var(--tpop15-d) * 100%), oklch(0.47 0.01 75));
--color-tpop15-hair: color-mix(in oklch, oklch(0.3 0.006 70) calc(var(--tpop15-d) * 100%), oklch(0.9 0.006 95));
--color-tpop15-ember: color-mix(in oklch, oklch(0.72 0.17 40) calc(var(--tpop15-d) * 100%), oklch(0.62 0.19 38));
--color-tpop15-emberdeep: color-mix(in oklch, oklch(0.78 0.15 42) calc(var(--tpop15-d) * 100%), oklch(0.5 0.17 36));
}
```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 Filter Dropdown with Checkboxes
Source: https://codefronts.com/snippets/tailwind-css-popovers/tailwind-filter-dropdown-checkboxes/
The clearest real use of popover="manual": a filter menu you tick several boxes in, which must not vanish because a click landed just outside it. The port makes the panel live with zero JS — a CSS counter ([counter-reset:tpop15] + checked:[counter-increment:tpop15]) prints how many filters are ticked, the chip shows a dot whenever any are, and a native type="reset" restores the defaults.
## HTML
```html
<section class="tpop-15 transition-[--tpop15-d] duration-300 ease-in-out motion-reduce:transition-none dark:[--tpop15-d:1] [[data-theme=dark]_&]:[--tpop15-d:1] [[data-theme=light]_&]:[--tpop15-d:0] group/root w-full min-h-svh bg-tpop15-bg px-4 py-9.5 font-tpop15-sans text-tpop15-ink" aria-label="Filter dropdown with checkboxes demo">
<div class="grid min-h-[calc(100svh-76px)] max-w-full place-items-center">
<div class="w-full min-w-0 max-w-[33rem] border-2 border-tpop15-ink bg-tpop15-surface shadow-[7px_7px_0_var(--color-tpop15-ember)]">
<div class="flex flex-wrap items-center justify-between gap-2.5 border-b-2 border-tpop15-ink px-3 py-2.5">
<span class="min-w-0 font-tpop15-mono text-[13px] font-semibold leading-tight">Shipments · 1,284 rows</span>
<button id="tpop-15-trigger" type="button" popovertarget="tpop-15-filters" popovertargetaction="show" class="relative inline-flex min-h-11 cursor-pointer items-center gap-2 border-2 border-tpop15-ink bg-tpop15-ink px-3 text-[12px] font-bold uppercase tracking-[0.05em] text-tpop15-surface transition-[translate,box-shadow] duration-100 [anchor-name:--tpop15-a] hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-[3px_3px_0_var(--color-tpop15-ember)] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember motion-reduce:transition-none">
<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M2.5 4h11M4.5 8h7M6.5 12h3"/></svg>
Filters
<span aria-hidden="true" class="absolute -top-1.5 -right-1.5 hidden size-3 border-2 border-tpop15-surface bg-tpop15-ember group-has-[#tpop-15-filters_input:checked]/root:block"></span>
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Ref</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Lane</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Status</th><th scope="col" class="border-b border-tpop15-hair px-3 py-2 text-right text-[10px] font-bold uppercase tracking-[0.16em] text-tpop15-mut">Weight</th></tr>
</thead>
<tbody class="font-tpop15-mono text-[12.5px]">
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8841</td><td class="px-3 py-2.5 whitespace-nowrap">Rotterdam → Leeds</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink/8 px-1.5 py-0.5 text-[11px]">transit</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">1,240 kg</td></tr>
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8842</td><td class="px-3 py-2.5 whitespace-nowrap">Gdansk → Bremen</td><td class="px-3 py-2.5"><span class="bg-tpop15-ember/15 px-1.5 py-0.5 text-[11px] text-tpop15-emberdeep">customs</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">860 kg</td></tr>
<tr class="border-b border-tpop15-hair"><td class="px-3 py-2.5">SH-8843</td><td class="px-3 py-2.5 whitespace-nowrap">Vigo → Nantes</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink/8 px-1.5 py-0.5 text-[11px]">transit</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">3,105 kg</td></tr>
<tr><td class="px-3 py-2.5">SH-8844</td><td class="px-3 py-2.5 whitespace-nowrap">Oslo → Aarhus</td><td class="px-3 py-2.5"><span class="bg-tpop15-ink px-1.5 py-0.5 text-[11px] text-tpop15-surface">exception</span></td><td class="px-3 py-2.5 text-right whitespace-nowrap text-tpop15-mut tabular-nums">412 kg</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div popover="manual" id="tpop-15-filters" role="group" aria-labelledby="tpop-15-legend" class="fixed m-auto w-[min(18rem,calc(100vw-24px))] border-2 border-tpop15-ink bg-tpop15-surface p-3.5 text-tpop15-ink opacity-0 shadow-[7px_7px_0_var(--color-tpop15-ember)] transition-[opacity,translate,display,overlay] duration-150 ease-out transition-discrete [position-anchor:--tpop15-a] [position-area:block-end_span-inline-start] [position-try-fallbacks:flip-block,flip-inline] open:opacity-100 starting:open:-translate-y-1.5 starting:open:opacity-0 motion-reduce:transition-none supports-[anchor-name:--a]:inset-auto supports-[anchor-name:--a]:m-0 supports-[anchor-name:--a]:mt-2.5">
<div class="mb-1.5 flex items-center justify-between gap-2">
<p id="tpop-15-legend" class="text-[11.5px] font-bold uppercase tracking-[0.16em]">Filter shipments</p>
<button type="button" popovertarget="tpop-15-filters" popovertargetaction="hide" aria-label="Close filters" class="-my-3 -mr-3 inline-flex size-11 cursor-pointer items-center justify-center hover:text-tpop15-emberdeep focus-visible:outline-3 focus-visible:-outline-offset-3 focus-visible:outline-tpop15-ember">
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>
</button>
</div>
<form class="[counter-reset:tpop15]">
<fieldset class="grid">
<legend class="mb-1 font-tpop15-mono text-[11px] font-semibold uppercase tracking-[0.1em] text-tpop15-mut">Status</legend>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> In transit</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Held at customs</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Delivered</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" checked class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Exception raised</label>
<label class="flex min-h-11 min-w-0 cursor-pointer items-center gap-2.5 text-[14px] hover:text-tpop15-emberdeep"><input type="checkbox" class="size-4.5 flex-none cursor-pointer accent-tpop15-ember checked:[counter-increment:tpop15] focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember"> Cancelled</label>
</fieldset>
<p class="mt-2 mb-3 flex items-center justify-between gap-2 border-t border-tpop15-hair pt-2.5 text-[12.5px] font-semibold">
<span><span class="font-tpop15-mono text-tpop15-emberdeep after:content-[counter(tpop15)]"></span> selected · click the table, I stay</span>
<button type="reset" class="min-h-11 cursor-pointer px-1 text-[12px] font-bold uppercase tracking-[0.05em] text-tpop15-mut underline underline-offset-4 hover:text-tpop15-ink focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember">Reset</button>
</p>
<div class="flex flex-wrap gap-2">
<button type="button" popovertarget="tpop-15-filters" popovertargetaction="hide" class="min-h-11 min-w-0 flex-auto cursor-pointer border-2 border-tpop15-ink bg-tpop15-ink px-3 text-[12px] font-bold uppercase tracking-[0.06em] text-tpop15-surface transition-colors hover:bg-tpop15-ember hover:border-tpop15-ember hover:text-tpop15-ink focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tpop15-ember">Apply filters</button>
</div>
</form>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpop15-bg: #f2f2ef;
--color-tpop15-bg: oklch(0.96 0.003 95);
--color-tpop15-surface: #ffffff;
--color-tpop15-surface: oklch(1 0 0);
--color-tpop15-ink: #181614;
--color-tpop15-ink: oklch(0.2 0.005 70);
--color-tpop15-mut: #5e5a55;
--color-tpop15-mut: oklch(0.47 0.01 75);
--color-tpop15-hair: #dfdeda;
--color-tpop15-hair: oklch(0.9 0.006 95);
--color-tpop15-ember: #e04f1a;
--color-tpop15-ember: oklch(0.62 0.19 38);
--color-tpop15-emberdeep: #af2f02;
--color-tpop15-emberdeep: oklch(0.5 0.17 36);
--font-tpop15-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpop15-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Theme dial: 0 = light, 1 = dark. Registered so the root can transition it. */
@property --tpop15-d {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
/* 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 — plus the dark palette. Each @theme colour is
re-declared on the root as a light/dark mix of the dial, so every utility (and every open
popover, which inherits from the root) follows it. Light value last, dark value first. */
.tpop-15 {
width: 100%;
min-height: 100svh;
--color-tpop15-bg: color-mix(in oklch, oklch(0.16 0.004 70) calc(var(--tpop15-d) * 100%), oklch(0.96 0.003 95));
--color-tpop15-surface: color-mix(in oklch, oklch(0.205 0.005 70) calc(var(--tpop15-d) * 100%), oklch(1 0 0));
--color-tpop15-ink: color-mix(in oklch, oklch(0.95 0.006 90) calc(var(--tpop15-d) * 100%), oklch(0.2 0.005 70));
--color-tpop15-mut: color-mix(in oklch, oklch(0.73 0.01 80) calc(var(--tpop15-d) * 100%), oklch(0.47 0.01 75));
--color-tpop15-hair: color-mix(in oklch, oklch(0.3 0.006 70) calc(var(--tpop15-d) * 100%), oklch(0.9 0.006 95));
--color-tpop15-ember: color-mix(in oklch, oklch(0.72 0.17 40) calc(var(--tpop15-d) * 100%), oklch(0.62 0.19 38));
--color-tpop15-emberdeep: color-mix(in oklch, oklch(0.78 0.15 42) calc(var(--tpop15-d) * 100%), oklch(0.5 0.17 36));
}
```How this works
Manual exists for this panel. Four ticks means four clicks in a small area; with auto, one miss dismisses the whole thing. Manual removes light dismiss and Escape, so the panel ships explicit exits: Apply and ✕ (popovertargetaction="hide").
A live count in pure CSS. Counters increment in document order, and the summary sits after the checkboxes inside the same form:
<form class="[counter-reset:tpop15]">
<input type="checkbox" class="checked:[counter-increment:tpop15]"> …
<span class="after:content-[counter(tpop15)]"></span> selected
<button type="reset">Reset</button>
</form>The chip can't count, but it can know. Counters don't flow backwards to the trigger, so the chip uses :has() instead — group-has-[#tpop-15-filters_input:checked]/root:block shows its dot whenever at least one box is ticked. (The underscore is the space in #tpop-15-filters input:checked.)
Make it yours
- Two columns on wide screens:
sm:grid-cols-2on the fieldset. - Clear instead of reset: set every box's default to unchecked, and
type="reset"becomes Clear all. - Feel what manual protects: switch the panel to
popover="auto"and try ticking fast.
Gotchas — read before shipping
- A manual popover with no visible close cannot be dismissed at all.
- Escape does nothing on manual; the ✕ must be in the tab order.
type="reset"restores the defaultcheckedattributes, not an empty state.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 125+ | 26+ | 131+ | 125+ |
Manual popovers and the hide action ship with the attribute; :has() is Chrome 105 / Safari 15.4 / Firefox 121; anchoring sets the floor. Unanchored, the panel centres and every control still works.