09 / 28
All 28 in 28 Tailwind CSS Close Buttons
- 01Tailwind Modal Close Button (Top-Right Corner X)
- 02Accessible Tailwind Close Button with Focus Ring (WCAG)
- 03iOS-Style Tailwind Tab Close Button
- 04Tailwind Floating Chat Widget Close Button
- 05Tailwind Cookie Consent Banner Dismiss Button
- 06Tailwind Mobile Ad Banner Close Button (48px Tap Target)
- 07Neumorphic Tailwind Close Button (Soft UI)
- 08Tailwind Hamburger to X Menu Button Animation
- 09Tailwind Video Player Overlay Close Button
- 10Tailwind Drawer Close Button with Native Dialog
- 11Origami Fold Tailwind Close Button
- 12Lock Tumbler Tailwind Close Button (@property)
- 13Rotate on Hover Tailwind Close Button
- 14Magnetic Cursor Tailwind Close Button
- 15Liquid Goo Tailwind Close Button (SVG Filter)
- 16Glitch Effect Tailwind Close Button
- 17Trash Compactor Tailwind Close Button
- 18Material Ripple Tailwind Close Button
- 19Particle Burst Tailwind Close Button
- 20Spring Snap Tailwind Close Button
- 21Neon Glow Tailwind Close Button
- 22Corner Ribbon Tailwind Close Button
- 233D Fold Away Tailwind Close Button
- 24Hold to Confirm Tailwind Close Button
- 25Tailwind Toast Notification Dismiss Button
- 26Tailwind Lightbox Image Close Button
- 27Brutalist Tailwind Close Button
- 28Galaxy Vortex Tailwind Close Button
Tailwind Video Player Overlay Close Button
Player chrome that gets out of the way: a gradient top bar with a blurred glass × that fades after 2.4s of stillness via group-data-idle/player:opacity-0. The port closes the original's keyboard gap — any key press or focus inside the player now wakes the chrome, and it never idles while focus is inside — and adds a matching bottom scrubber that hides with it.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tcb-09 w-full min-h-svh grid place-items-center bg-tcb09-bg px-6 py-12 font-tcb09-sans" aria-label="Video player overlay close button demo">
<div class="grid w-full justify-items-center gap-7">
<div id="tcb-09-player" class="group/player relative aspect-video w-full max-w-[28rem] overflow-hidden rounded-2xl shadow-[0_26px_60px_-28px_rgb(0_0_0/0.9)] ring-1 ring-white/10">
<div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(80%_70%_at_30%_25%,var(--color-tcb09-v1),transparent_55%),linear-gradient(200deg,var(--color-tcb09-v2),var(--color-tcb09-v3)_55%,var(--color-tcb09-v4))]">
<span class="grid size-15 place-items-center rounded-full bg-black/45 backdrop-blur-sm"><span class="ms-1 size-5 bg-white [clip-path:polygon(0_0,100%_50%,0_100%)]"></span></span>
</div>
<div class="absolute inset-x-0 top-0 flex items-center justify-between gap-3 bg-linear-to-b from-black/62 to-transparent pt-3 pe-3 pb-7 ps-4 transition-opacity duration-300 group-data-idle/player:pointer-events-none group-data-idle/player:opacity-0 motion-reduce:transition-none">
<span class="truncate text-[13px] font-medium text-white [text-shadow:0_1px_4px_rgb(0_0_0/0.5)]">Trail running the Dolomites</span>
<button type="button" aria-label="Close player" class="group grid size-11 flex-none cursor-pointer place-items-center rounded-full border border-white/16 bg-[rgb(15_15_18/0.55)] backdrop-blur-sm transition duration-150 hover:bg-[rgb(15_15_18/0.8)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-white active:scale-90 motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-3.5 before:absolute before:top-1/2 before:left-1/2 before:h-[1.8px] before:w-3.5 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-white after:absolute after:top-1/2 after:left-1/2 after:h-[1.8px] after:w-3.5 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-white"></span>
</button>
</div>
<div aria-hidden="true" class="absolute inset-x-0 bottom-0 flex items-center gap-3 bg-linear-to-t from-black/60 to-transparent px-4 pt-6 pb-3 font-tcb09-mono text-[11px] text-white transition-opacity duration-300 group-data-idle/player:opacity-0 motion-reduce:transition-none">
<span>1:12</span>
<span class="relative h-1 flex-1 overflow-hidden rounded-full bg-white/25"><span class="absolute inset-y-0 start-0 w-[38%] rounded-full bg-white"></span></span>
<span>3:08</span>
</div>
</div>
<p aria-hidden="true" class="rounded-full px-3.5 py-2 font-tcb09-mono text-[11.5px] text-tcb09-mut ring-1 ring-white/12">group-data-idle/player:opacity-0 · wakes on move, key & focus</p>
</div>
</section><section class="tcb-09 w-full min-h-svh grid place-items-center bg-tcb09-bg px-6 py-12 font-tcb09-sans" aria-label="Video player overlay close button demo">
<div class="grid w-full justify-items-center gap-7">
<div id="tcb-09-player" class="group/player relative aspect-video w-full max-w-[28rem] overflow-hidden rounded-2xl shadow-[0_26px_60px_-28px_rgb(0_0_0/0.9)] ring-1 ring-white/10">
<div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(80%_70%_at_30%_25%,var(--color-tcb09-v1),transparent_55%),linear-gradient(200deg,var(--color-tcb09-v2),var(--color-tcb09-v3)_55%,var(--color-tcb09-v4))]">
<span class="grid size-15 place-items-center rounded-full bg-black/45 backdrop-blur-sm"><span class="ms-1 size-5 bg-white [clip-path:polygon(0_0,100%_50%,0_100%)]"></span></span>
</div>
<div class="absolute inset-x-0 top-0 flex items-center justify-between gap-3 bg-linear-to-b from-black/62 to-transparent pt-3 pe-3 pb-7 ps-4 transition-opacity duration-300 group-data-idle/player:pointer-events-none group-data-idle/player:opacity-0 motion-reduce:transition-none">
<span class="truncate text-[13px] font-medium text-white [text-shadow:0_1px_4px_rgb(0_0_0/0.5)]">Trail running the Dolomites</span>
<button type="button" aria-label="Close player" class="group grid size-11 flex-none cursor-pointer place-items-center rounded-full border border-white/16 bg-[rgb(15_15_18/0.55)] backdrop-blur-sm transition duration-150 hover:bg-[rgb(15_15_18/0.8)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-white active:scale-90 motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-3.5 before:absolute before:top-1/2 before:left-1/2 before:h-[1.8px] before:w-3.5 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-white after:absolute after:top-1/2 after:left-1/2 after:h-[1.8px] after:w-3.5 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-white"></span>
</button>
</div>
<div aria-hidden="true" class="absolute inset-x-0 bottom-0 flex items-center gap-3 bg-linear-to-t from-black/60 to-transparent px-4 pt-6 pb-3 font-tcb09-mono text-[11px] text-white transition-opacity duration-300 group-data-idle/player:opacity-0 motion-reduce:transition-none">
<span>1:12</span>
<span class="relative h-1 flex-1 overflow-hidden rounded-full bg-white/25"><span class="absolute inset-y-0 start-0 w-[38%] rounded-full bg-white"></span></span>
<span>3:08</span>
</div>
</div>
<p aria-hidden="true" class="rounded-full px-3.5 py-2 font-tcb09-mono text-[11.5px] text-tcb09-mut ring-1 ring-white/12">group-data-idle/player:opacity-0 · wakes on move, key & focus</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-tcb09-bg: #0c0e12;
--color-tcb09-bg: oklch(0.14 0.01 260);
--color-tcb09-mut: #a6aab5;
--color-tcb09-mut: oklch(0.74 0.015 260);
--color-tcb09-v1: #b9d6e6;
--color-tcb09-v1: oklch(0.8 0.06 220);
--color-tcb09-v2: #6ea3cf;
--color-tcb09-v2: oklch(0.72 0.1 230);
--color-tcb09-v3: #3e5790;
--color-tcb09-v3: oklch(0.45 0.09 260);
--color-tcb09-v4: #1f1d3a;
--color-tcb09-v4: oklch(0.25 0.05 280);
--font-tcb09-sans: 'Geist', system-ui, sans-serif;
--font-tcb09-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. */
.tcb-09 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tcb09-bg: #0c0e12;
--color-tcb09-bg: oklch(0.14 0.01 260);
--color-tcb09-mut: #a6aab5;
--color-tcb09-mut: oklch(0.74 0.015 260);
--color-tcb09-v1: #b9d6e6;
--color-tcb09-v1: oklch(0.8 0.06 220);
--color-tcb09-v2: #6ea3cf;
--color-tcb09-v2: oklch(0.72 0.1 230);
--color-tcb09-v3: #3e5790;
--color-tcb09-v3: oklch(0.45 0.09 260);
--color-tcb09-v4: #1f1d3a;
--color-tcb09-v4: oklch(0.25 0.05 280);
--font-tcb09-sans: 'Geist', system-ui, sans-serif;
--font-tcb09-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. */
.tcb-09 {
width: 100%;
min-height: 100svh;
}(() => {
const player = document.getElementById('tcb-09-player');
if (!player || player.dataset.wired) return;
player.dataset.wired = '1';
const IDLE = 2400;
let timer = 0;
const sleep = () => { if (!player.matches(':focus-within')) player.setAttribute('data-idle', ''); };
const wake = () => {
player.removeAttribute('data-idle');
clearTimeout(timer);
timer = setTimeout(sleep, IDLE);
};
['pointermove', 'pointerdown', 'keydown', 'focusin'].forEach((ev) => player.addEventListener(ev, wake));
player.addEventListener('focusout', wake);
wake();
})();(() => {
const player = document.getElementById('tcb-09-player');
if (!player || player.dataset.wired) return;
player.dataset.wired = '1';
const IDLE = 2400;
let timer = 0;
const sleep = () => { if (!player.matches(':focus-within')) player.setAttribute('data-idle', ''); };
const wake = () => {
player.removeAttribute('data-idle');
clearTimeout(timer);
timer = setTimeout(sleep, IDLE);
};
['pointermove', 'pointerdown', 'keydown', 'focusin'].forEach((ev) => player.addEventListener(ev, wake));
player.addEventListener('focusout', wake);
wake();
})();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-tcb09-bg: #0c0e12;
--color-tcb09-bg: oklch(0.14 0.01 260);
--color-tcb09-mut: #a6aab5;
--color-tcb09-mut: oklch(0.74 0.015 260);
--color-tcb09-v1: #b9d6e6;
--color-tcb09-v1: oklch(0.8 0.06 220);
--color-tcb09-v2: #6ea3cf;
--color-tcb09-v2: oklch(0.72 0.1 230);
--color-tcb09-v3: #3e5790;
--color-tcb09-v3: oklch(0.45 0.09 260);
--color-tcb09-v4: #1f1d3a;
--color-tcb09-v4: oklch(0.25 0.05 280);
--font-tcb09-sans: 'Geist', system-ui, sans-serif;
--font-tcb09-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. */
.tcb-09 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tcb09-bg: #0c0e12;
--color-tcb09-bg: oklch(0.14 0.01 260);
--color-tcb09-mut: #a6aab5;
--color-tcb09-mut: oklch(0.74 0.015 260);
--color-tcb09-v1: #b9d6e6;
--color-tcb09-v1: oklch(0.8 0.06 220);
--color-tcb09-v2: #6ea3cf;
--color-tcb09-v2: oklch(0.72 0.1 230);
--color-tcb09-v3: #3e5790;
--color-tcb09-v3: oklch(0.45 0.09 260);
--color-tcb09-v4: #1f1d3a;
--color-tcb09-v4: oklch(0.25 0.05 280);
--font-tcb09-sans: 'Geist', system-ui, sans-serif;
--font-tcb09-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. */
.tcb-09 {
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.
.is-idle .topbarand:focus-within .topbarbecamegroup-data-idle/player:utilities; the script setsdata-idleand never idles while focus is inside.- UI upgrade from the CSS original: the × grew from 36px to 44px, the chrome now wakes on
keydownandfocusin(keyboard users could previously tab to an invisible button), and a bottom scrubber fades with the top bar. - UI upgrade: the ▶ text glyph became a
clip-pathtriangle that renders identically on every platform, and the stage hint rose from 55% white to a 7:1 token.
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 Video Player Overlay Close Button
Source: https://codefronts.com/components/tailwind-css-close-buttons/tailwind-video-player-overlay-close-button/
Player chrome that gets out of the way: a gradient top bar with a blurred glass × that fades after 2.4s of stillness via group-data-idle/player:opacity-0. The port closes the original's keyboard gap — any key press or focus inside the player now wakes the chrome, and it never idles while focus is inside — and adds a matching bottom scrubber that hides with it.
## HTML
```html
<section class="tcb-09 w-full min-h-svh grid place-items-center bg-tcb09-bg px-6 py-12 font-tcb09-sans" aria-label="Video player overlay close button demo">
<div class="grid w-full justify-items-center gap-7">
<div id="tcb-09-player" class="group/player relative aspect-video w-full max-w-[28rem] overflow-hidden rounded-2xl shadow-[0_26px_60px_-28px_rgb(0_0_0/0.9)] ring-1 ring-white/10">
<div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(80%_70%_at_30%_25%,var(--color-tcb09-v1),transparent_55%),linear-gradient(200deg,var(--color-tcb09-v2),var(--color-tcb09-v3)_55%,var(--color-tcb09-v4))]">
<span class="grid size-15 place-items-center rounded-full bg-black/45 backdrop-blur-sm"><span class="ms-1 size-5 bg-white [clip-path:polygon(0_0,100%_50%,0_100%)]"></span></span>
</div>
<div class="absolute inset-x-0 top-0 flex items-center justify-between gap-3 bg-linear-to-b from-black/62 to-transparent pt-3 pe-3 pb-7 ps-4 transition-opacity duration-300 group-data-idle/player:pointer-events-none group-data-idle/player:opacity-0 motion-reduce:transition-none">
<span class="truncate text-[13px] font-medium text-white [text-shadow:0_1px_4px_rgb(0_0_0/0.5)]">Trail running the Dolomites</span>
<button type="button" aria-label="Close player" class="group grid size-11 flex-none cursor-pointer place-items-center rounded-full border border-white/16 bg-[rgb(15_15_18/0.55)] backdrop-blur-sm transition duration-150 hover:bg-[rgb(15_15_18/0.8)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-white active:scale-90 motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-3.5 before:absolute before:top-1/2 before:left-1/2 before:h-[1.8px] before:w-3.5 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-white after:absolute after:top-1/2 after:left-1/2 after:h-[1.8px] after:w-3.5 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-white"></span>
</button>
</div>
<div aria-hidden="true" class="absolute inset-x-0 bottom-0 flex items-center gap-3 bg-linear-to-t from-black/60 to-transparent px-4 pt-6 pb-3 font-tcb09-mono text-[11px] text-white transition-opacity duration-300 group-data-idle/player:opacity-0 motion-reduce:transition-none">
<span>1:12</span>
<span class="relative h-1 flex-1 overflow-hidden rounded-full bg-white/25"><span class="absolute inset-y-0 start-0 w-[38%] rounded-full bg-white"></span></span>
<span>3:08</span>
</div>
</div>
<p aria-hidden="true" class="rounded-full px-3.5 py-2 font-tcb09-mono text-[11.5px] text-tcb09-mut ring-1 ring-white/12">group-data-idle/player:opacity-0 · wakes on move, key & focus</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcb09-bg: #0c0e12;
--color-tcb09-bg: oklch(0.14 0.01 260);
--color-tcb09-mut: #a6aab5;
--color-tcb09-mut: oklch(0.74 0.015 260);
--color-tcb09-v1: #b9d6e6;
--color-tcb09-v1: oklch(0.8 0.06 220);
--color-tcb09-v2: #6ea3cf;
--color-tcb09-v2: oklch(0.72 0.1 230);
--color-tcb09-v3: #3e5790;
--color-tcb09-v3: oklch(0.45 0.09 260);
--color-tcb09-v4: #1f1d3a;
--color-tcb09-v4: oklch(0.25 0.05 280);
--font-tcb09-sans: 'Geist', system-ui, sans-serif;
--font-tcb09-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. */
.tcb-09 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const player = document.getElementById('tcb-09-player');
if (!player || player.dataset.wired) return;
player.dataset.wired = '1';
const IDLE = 2400;
let timer = 0;
const sleep = () => { if (!player.matches(':focus-within')) player.setAttribute('data-idle', ''); };
const wake = () => {
player.removeAttribute('data-idle');
clearTimeout(timer);
timer = setTimeout(sleep, IDLE);
};
['pointermove', 'pointerdown', 'keydown', 'focusin'].forEach((ev) => player.addEventListener(ev, wake));
player.addEventListener('focusout', wake);
wake();
})();
```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 Video Player Overlay Close Button
Source: https://codefronts.com/components/tailwind-css-close-buttons/tailwind-video-player-overlay-close-button/
Player chrome that gets out of the way: a gradient top bar with a blurred glass × that fades after 2.4s of stillness via group-data-idle/player:opacity-0. The port closes the original's keyboard gap — any key press or focus inside the player now wakes the chrome, and it never idles while focus is inside — and adds a matching bottom scrubber that hides with it.
## HTML
```html
<section class="tcb-09 w-full min-h-svh grid place-items-center bg-tcb09-bg px-6 py-12 font-tcb09-sans" aria-label="Video player overlay close button demo">
<div class="grid w-full justify-items-center gap-7">
<div id="tcb-09-player" class="group/player relative aspect-video w-full max-w-[28rem] overflow-hidden rounded-2xl shadow-[0_26px_60px_-28px_rgb(0_0_0/0.9)] ring-1 ring-white/10">
<div aria-hidden="true" class="absolute inset-0 grid place-items-center bg-[radial-gradient(80%_70%_at_30%_25%,var(--color-tcb09-v1),transparent_55%),linear-gradient(200deg,var(--color-tcb09-v2),var(--color-tcb09-v3)_55%,var(--color-tcb09-v4))]">
<span class="grid size-15 place-items-center rounded-full bg-black/45 backdrop-blur-sm"><span class="ms-1 size-5 bg-white [clip-path:polygon(0_0,100%_50%,0_100%)]"></span></span>
</div>
<div class="absolute inset-x-0 top-0 flex items-center justify-between gap-3 bg-linear-to-b from-black/62 to-transparent pt-3 pe-3 pb-7 ps-4 transition-opacity duration-300 group-data-idle/player:pointer-events-none group-data-idle/player:opacity-0 motion-reduce:transition-none">
<span class="truncate text-[13px] font-medium text-white [text-shadow:0_1px_4px_rgb(0_0_0/0.5)]">Trail running the Dolomites</span>
<button type="button" aria-label="Close player" class="group grid size-11 flex-none cursor-pointer place-items-center rounded-full border border-white/16 bg-[rgb(15_15_18/0.55)] backdrop-blur-sm transition duration-150 hover:bg-[rgb(15_15_18/0.8)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-white active:scale-90 motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-3.5 before:absolute before:top-1/2 before:left-1/2 before:h-[1.8px] before:w-3.5 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-white after:absolute after:top-1/2 after:left-1/2 after:h-[1.8px] after:w-3.5 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-white"></span>
</button>
</div>
<div aria-hidden="true" class="absolute inset-x-0 bottom-0 flex items-center gap-3 bg-linear-to-t from-black/60 to-transparent px-4 pt-6 pb-3 font-tcb09-mono text-[11px] text-white transition-opacity duration-300 group-data-idle/player:opacity-0 motion-reduce:transition-none">
<span>1:12</span>
<span class="relative h-1 flex-1 overflow-hidden rounded-full bg-white/25"><span class="absolute inset-y-0 start-0 w-[38%] rounded-full bg-white"></span></span>
<span>3:08</span>
</div>
</div>
<p aria-hidden="true" class="rounded-full px-3.5 py-2 font-tcb09-mono text-[11.5px] text-tcb09-mut ring-1 ring-white/12">group-data-idle/player:opacity-0 · wakes on move, key & focus</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcb09-bg: #0c0e12;
--color-tcb09-bg: oklch(0.14 0.01 260);
--color-tcb09-mut: #a6aab5;
--color-tcb09-mut: oklch(0.74 0.015 260);
--color-tcb09-v1: #b9d6e6;
--color-tcb09-v1: oklch(0.8 0.06 220);
--color-tcb09-v2: #6ea3cf;
--color-tcb09-v2: oklch(0.72 0.1 230);
--color-tcb09-v3: #3e5790;
--color-tcb09-v3: oklch(0.45 0.09 260);
--color-tcb09-v4: #1f1d3a;
--color-tcb09-v4: oklch(0.25 0.05 280);
--font-tcb09-sans: 'Geist', system-ui, sans-serif;
--font-tcb09-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. */
.tcb-09 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const player = document.getElementById('tcb-09-player');
if (!player || player.dataset.wired) return;
player.dataset.wired = '1';
const IDLE = 2400;
let timer = 0;
const sleep = () => { if (!player.matches(':focus-within')) player.setAttribute('data-idle', ''); };
const wake = () => {
player.removeAttribute('data-idle');
clearTimeout(timer);
timer = setTimeout(sleep, IDLE);
};
['pointermove', 'pointerdown', 'keydown', 'focusin'].forEach((ev) => player.addEventListener(ev, wake));
player.addEventListener('focusout', wake);
wake();
})();
```How this works
One attribute, many listeners. Every chrome element reads the player's idle flag:
<div class="group/player …" data-idle>
<div class="… transition-opacity group-data-idle/player:pointer-events-none
group-data-idle/player:opacity-0">Keyboard parity. The script wakes on pointermove, pointerdown, keydown and focusin, and refuses to idle while :focus-within matches — so a keyboard user tabbing to the × can always see it.
Make it yours
- Idle delay: change
IDLEin the script (YouTube uses ~3s). - Always-on close: move the × out of the fading bar if your product requires a persistent exit.
- Light scrim:
from-black/40for bright footage.
Gotchas — read before shipping
- Hidden chrome must also be
pointer-events-none, or invisible buttons steal clicks meant for the video. - Do not idle on touch devices before the first tap, or the controls vanish before the user has seen them.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
The floor is Tailwind v4's generated sheet.