25 / 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 Toast Notification Dismiss Button
A success toast with a draining countdown bar that owns the clock: animate-tcb25-life shrinks it over 5s and animationend dismisses the toast — no duplicate timer. Hover or keyboard focus pauses it with group-hover/toast:[animation-play-state:paused], satisfying WCAG 2.2.1 (Timing Adjustable). The × is a 40px padded target pulled into the corner with negative margins.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tcb-25 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tcb25-bg),var(--color-tcb25-bg2))] px-6 py-12 font-tcb25-sans" aria-label="Toast notification dismiss button demo">
<div class="grid w-full justify-items-center gap-7">
<div class="grid w-full max-w-[23rem] justify-items-center gap-4">
<div id="tcb-25-toast" role="status" class="group/toast relative flex w-full items-center gap-3 overflow-hidden rounded-[14px] border border-tcb25-line/60 bg-tcb25-card py-3.5 ps-4 pe-3 shadow-[0_20px_46px_-24px_rgb(0_0_0/0.85)] transition duration-300 data-leaving:translate-x-6 data-leaving:opacity-0 motion-reduce:transition-none">
<span aria-hidden="true" class="grid size-7 flex-none place-items-center rounded-full bg-tcb25-edge/25 text-tcb25-edge"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m3.5 8.5 3 3 6-7"/></svg></span>
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
<strong class="text-[13.5px] font-semibold text-tcb25-ink">Changes saved</strong>
<span class="text-xs text-tcb25-mut">Hover or focus to pause the countdown</span>
</div>
<button id="tcb-25-close" type="button" aria-label="Dismiss notification" class="relative -my-2 grid size-11 flex-none cursor-pointer place-items-center rounded-[10px] transition-colors hover:bg-white/8 focus-visible:outline-[2.5px] focus-visible:-outline-offset-2 focus-visible:outline-tcb25-edge motion-reduce:transition-none">
<span aria-hidden="true" class="relative block size-4 before:absolute before:top-1/2 before:left-1/2 before:h-0.5 before:w-4 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-tcb25-x after:absolute after:top-1/2 after:left-1/2 after:h-0.5 after:w-4 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-tcb25-x"></span>
</button>
<span id="tcb-25-bar" aria-hidden="true" class="absolute inset-x-0 bottom-0 h-[3px] origin-left bg-tcb25-edge animate-tcb25-life group-hover/toast:[animation-play-state:paused] group-focus-within/toast:[animation-play-state:paused] motion-reduce:[animation-duration:20s]"></span>
</div>
<button id="tcb-25-reset" type="button" hidden class="min-h-11 cursor-pointer px-2 text-[13px] font-medium text-tcb25-mut underline decoration-dashed underline-offset-4 hover:text-tcb25-ink focus-visible:outline-2 focus-visible:outline-tcb25-edge">Show toast again</button>
</div>
<p aria-hidden="true" class="rounded-full px-3.5 py-2 font-tcb25-mono text-[11.5px] text-tcb25-mut ring-1 ring-white/12">animate-tcb25-life · group-focus-within/toast:[animation-play-state:paused]</p>
</div>
</section><section class="tcb-25 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tcb25-bg),var(--color-tcb25-bg2))] px-6 py-12 font-tcb25-sans" aria-label="Toast notification dismiss button demo">
<div class="grid w-full justify-items-center gap-7">
<div class="grid w-full max-w-[23rem] justify-items-center gap-4">
<div id="tcb-25-toast" role="status" class="group/toast relative flex w-full items-center gap-3 overflow-hidden rounded-[14px] border border-tcb25-line/60 bg-tcb25-card py-3.5 ps-4 pe-3 shadow-[0_20px_46px_-24px_rgb(0_0_0/0.85)] transition duration-300 data-leaving:translate-x-6 data-leaving:opacity-0 motion-reduce:transition-none">
<span aria-hidden="true" class="grid size-7 flex-none place-items-center rounded-full bg-tcb25-edge/25 text-tcb25-edge"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m3.5 8.5 3 3 6-7"/></svg></span>
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
<strong class="text-[13.5px] font-semibold text-tcb25-ink">Changes saved</strong>
<span class="text-xs text-tcb25-mut">Hover or focus to pause the countdown</span>
</div>
<button id="tcb-25-close" type="button" aria-label="Dismiss notification" class="relative -my-2 grid size-11 flex-none cursor-pointer place-items-center rounded-[10px] transition-colors hover:bg-white/8 focus-visible:outline-[2.5px] focus-visible:-outline-offset-2 focus-visible:outline-tcb25-edge motion-reduce:transition-none">
<span aria-hidden="true" class="relative block size-4 before:absolute before:top-1/2 before:left-1/2 before:h-0.5 before:w-4 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-tcb25-x after:absolute after:top-1/2 after:left-1/2 after:h-0.5 after:w-4 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-tcb25-x"></span>
</button>
<span id="tcb-25-bar" aria-hidden="true" class="absolute inset-x-0 bottom-0 h-[3px] origin-left bg-tcb25-edge animate-tcb25-life group-hover/toast:[animation-play-state:paused] group-focus-within/toast:[animation-play-state:paused] motion-reduce:[animation-duration:20s]"></span>
</div>
<button id="tcb-25-reset" type="button" hidden class="min-h-11 cursor-pointer px-2 text-[13px] font-medium text-tcb25-mut underline decoration-dashed underline-offset-4 hover:text-tcb25-ink focus-visible:outline-2 focus-visible:outline-tcb25-edge">Show toast again</button>
</div>
<p aria-hidden="true" class="rounded-full px-3.5 py-2 font-tcb25-mono text-[11.5px] text-tcb25-mut ring-1 ring-white/12">animate-tcb25-life · group-focus-within/toast:[animation-play-state:paused]</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-tcb25-bg: #1e2430;
--color-tcb25-bg: oklch(0.23 0.02 250);
--color-tcb25-bg2: #141922;
--color-tcb25-bg2: oklch(0.17 0.015 250);
--color-tcb25-card: #2a303c;
--color-tcb25-card: oklch(0.28 0.02 250);
--color-tcb25-line: #4a5160;
--color-tcb25-line: oklch(0.4 0.02 250);
--color-tcb25-ink: #eceef3;
--color-tcb25-ink: oklch(0.94 0.01 250);
--color-tcb25-mut: #a9afbb;
--color-tcb25-mut: oklch(0.75 0.02 250);
--color-tcb25-x: #c3c8d2;
--color-tcb25-x: oklch(0.83 0.02 250);
--color-tcb25-edge: #3fcf94;
--color-tcb25-edge: oklch(0.76 0.15 160);
--font-tcb25-sans: 'Geist', system-ui, sans-serif;
--font-tcb25-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tcb25-life: tcb25-life 5s linear forwards;
@keyframes tcb25-life {
from {
transform: scaleX(1);
}
to {
transform: scaleX(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. */
.tcb-25 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tcb25-bg: #1e2430;
--color-tcb25-bg: oklch(0.23 0.02 250);
--color-tcb25-bg2: #141922;
--color-tcb25-bg2: oklch(0.17 0.015 250);
--color-tcb25-card: #2a303c;
--color-tcb25-card: oklch(0.28 0.02 250);
--color-tcb25-line: #4a5160;
--color-tcb25-line: oklch(0.4 0.02 250);
--color-tcb25-ink: #eceef3;
--color-tcb25-ink: oklch(0.94 0.01 250);
--color-tcb25-mut: #a9afbb;
--color-tcb25-mut: oklch(0.75 0.02 250);
--color-tcb25-x: #c3c8d2;
--color-tcb25-x: oklch(0.83 0.02 250);
--color-tcb25-edge: #3fcf94;
--color-tcb25-edge: oklch(0.76 0.15 160);
--font-tcb25-sans: 'Geist', system-ui, sans-serif;
--font-tcb25-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tcb25-life: tcb25-life 5s linear forwards;
@keyframes tcb25-life {
from {
transform: scaleX(1);
}
to {
transform: scaleX(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. */
.tcb-25 {
width: 100%;
min-height: 100svh;
}(() => {
const toast = document.getElementById('tcb-25-toast');
if (!toast || toast.dataset.wired) return;
toast.dataset.wired = '1';
const bar = document.getElementById('tcb-25-bar');
const reset = document.getElementById('tcb-25-reset');
const leave = () => {
if (toast.hasAttribute('data-leaving')) return;
toast.setAttribute('data-leaving', '');
setTimeout(() => { toast.hidden = true; reset.hidden = false; reset.focus(); }, 300);
};
bar.addEventListener('animationend', leave);
document.getElementById('tcb-25-close').addEventListener('click', leave);
reset.addEventListener('click', () => {
toast.hidden = false;
toast.removeAttribute('data-leaving');
reset.hidden = true;
bar.style.animation = 'none';
requestAnimationFrame(() => { bar.style.animation = ''; });
});
})();(() => {
const toast = document.getElementById('tcb-25-toast');
if (!toast || toast.dataset.wired) return;
toast.dataset.wired = '1';
const bar = document.getElementById('tcb-25-bar');
const reset = document.getElementById('tcb-25-reset');
const leave = () => {
if (toast.hasAttribute('data-leaving')) return;
toast.setAttribute('data-leaving', '');
setTimeout(() => { toast.hidden = true; reset.hidden = false; reset.focus(); }, 300);
};
bar.addEventListener('animationend', leave);
document.getElementById('tcb-25-close').addEventListener('click', leave);
reset.addEventListener('click', () => {
toast.hidden = false;
toast.removeAttribute('data-leaving');
reset.hidden = true;
bar.style.animation = 'none';
requestAnimationFrame(() => { bar.style.animation = ''; });
});
})();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-tcb25-bg: #1e2430;
--color-tcb25-bg: oklch(0.23 0.02 250);
--color-tcb25-bg2: #141922;
--color-tcb25-bg2: oklch(0.17 0.015 250);
--color-tcb25-card: #2a303c;
--color-tcb25-card: oklch(0.28 0.02 250);
--color-tcb25-line: #4a5160;
--color-tcb25-line: oklch(0.4 0.02 250);
--color-tcb25-ink: #eceef3;
--color-tcb25-ink: oklch(0.94 0.01 250);
--color-tcb25-mut: #a9afbb;
--color-tcb25-mut: oklch(0.75 0.02 250);
--color-tcb25-x: #c3c8d2;
--color-tcb25-x: oklch(0.83 0.02 250);
--color-tcb25-edge: #3fcf94;
--color-tcb25-edge: oklch(0.76 0.15 160);
--font-tcb25-sans: 'Geist', system-ui, sans-serif;
--font-tcb25-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tcb25-life: tcb25-life 5s linear forwards;
@keyframes tcb25-life {
from {
transform: scaleX(1);
}
to {
transform: scaleX(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. */
.tcb-25 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tcb25-bg: #1e2430;
--color-tcb25-bg: oklch(0.23 0.02 250);
--color-tcb25-bg2: #141922;
--color-tcb25-bg2: oklch(0.17 0.015 250);
--color-tcb25-card: #2a303c;
--color-tcb25-card: oklch(0.28 0.02 250);
--color-tcb25-line: #4a5160;
--color-tcb25-line: oklch(0.4 0.02 250);
--color-tcb25-ink: #eceef3;
--color-tcb25-ink: oklch(0.94 0.01 250);
--color-tcb25-mut: #a9afbb;
--color-tcb25-mut: oklch(0.75 0.02 250);
--color-tcb25-x: #c3c8d2;
--color-tcb25-x: oklch(0.83 0.02 250);
--color-tcb25-edge: #3fcf94;
--color-tcb25-edge: oklch(0.76 0.15 160);
--font-tcb25-sans: 'Geist', system-ui, sans-serif;
--font-tcb25-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tcb25-life: tcb25-life 5s linear forwards;
@keyframes tcb25-life {
from {
transform: scaleX(1);
}
to {
transform: scaleX(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. */
.tcb-25 {
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-leavingbecamedata-leaving:; the countdown keyframes moved into@themeas--animate-tcb25-life.- UI upgrade from the CSS original: the countdown now pauses on keyboard focus as well as hover (
group-focus-within/toast:) — previously a keyboard user tabbing to the × could watch the toast vanish underneath them, failing WCAG 2.2.1. - UI upgrade: the × hit area grew from 40px to 44px, the text ✓ became a crisp SVG check, double-dismiss is guarded, and subtitle copy rose from
oklch(0.65)to0.75for 6.2:1.
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 Toast Notification Dismiss Button
Source: https://codefronts.com/components/tailwind-css-close-buttons/toast-dismiss/
A success toast with a draining countdown bar that owns the clock: animate-tcb25-life shrinks it over 5s and animationend dismisses the toast — no duplicate timer. Hover or keyboard focus pauses it with group-hover/toast:[animation-play-state:paused], satisfying WCAG 2.2.1 (Timing Adjustable). The × is a 40px padded target pulled into the corner with negative margins.
## HTML
```html
<section class="tcb-25 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tcb25-bg),var(--color-tcb25-bg2))] px-6 py-12 font-tcb25-sans" aria-label="Toast notification dismiss button demo">
<div class="grid w-full justify-items-center gap-7">
<div class="grid w-full max-w-[23rem] justify-items-center gap-4">
<div id="tcb-25-toast" role="status" class="group/toast relative flex w-full items-center gap-3 overflow-hidden rounded-[14px] border border-tcb25-line/60 bg-tcb25-card py-3.5 ps-4 pe-3 shadow-[0_20px_46px_-24px_rgb(0_0_0/0.85)] transition duration-300 data-leaving:translate-x-6 data-leaving:opacity-0 motion-reduce:transition-none">
<span aria-hidden="true" class="grid size-7 flex-none place-items-center rounded-full bg-tcb25-edge/25 text-tcb25-edge"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m3.5 8.5 3 3 6-7"/></svg></span>
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
<strong class="text-[13.5px] font-semibold text-tcb25-ink">Changes saved</strong>
<span class="text-xs text-tcb25-mut">Hover or focus to pause the countdown</span>
</div>
<button id="tcb-25-close" type="button" aria-label="Dismiss notification" class="relative -my-2 grid size-11 flex-none cursor-pointer place-items-center rounded-[10px] transition-colors hover:bg-white/8 focus-visible:outline-[2.5px] focus-visible:-outline-offset-2 focus-visible:outline-tcb25-edge motion-reduce:transition-none">
<span aria-hidden="true" class="relative block size-4 before:absolute before:top-1/2 before:left-1/2 before:h-0.5 before:w-4 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-tcb25-x after:absolute after:top-1/2 after:left-1/2 after:h-0.5 after:w-4 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-tcb25-x"></span>
</button>
<span id="tcb-25-bar" aria-hidden="true" class="absolute inset-x-0 bottom-0 h-[3px] origin-left bg-tcb25-edge animate-tcb25-life group-hover/toast:[animation-play-state:paused] group-focus-within/toast:[animation-play-state:paused] motion-reduce:[animation-duration:20s]"></span>
</div>
<button id="tcb-25-reset" type="button" hidden class="min-h-11 cursor-pointer px-2 text-[13px] font-medium text-tcb25-mut underline decoration-dashed underline-offset-4 hover:text-tcb25-ink focus-visible:outline-2 focus-visible:outline-tcb25-edge">Show toast again</button>
</div>
<p aria-hidden="true" class="rounded-full px-3.5 py-2 font-tcb25-mono text-[11.5px] text-tcb25-mut ring-1 ring-white/12">animate-tcb25-life · group-focus-within/toast:[animation-play-state:paused]</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcb25-bg: #1e2430;
--color-tcb25-bg: oklch(0.23 0.02 250);
--color-tcb25-bg2: #141922;
--color-tcb25-bg2: oklch(0.17 0.015 250);
--color-tcb25-card: #2a303c;
--color-tcb25-card: oklch(0.28 0.02 250);
--color-tcb25-line: #4a5160;
--color-tcb25-line: oklch(0.4 0.02 250);
--color-tcb25-ink: #eceef3;
--color-tcb25-ink: oklch(0.94 0.01 250);
--color-tcb25-mut: #a9afbb;
--color-tcb25-mut: oklch(0.75 0.02 250);
--color-tcb25-x: #c3c8d2;
--color-tcb25-x: oklch(0.83 0.02 250);
--color-tcb25-edge: #3fcf94;
--color-tcb25-edge: oklch(0.76 0.15 160);
--font-tcb25-sans: 'Geist', system-ui, sans-serif;
--font-tcb25-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tcb25-life: tcb25-life 5s linear forwards;
@keyframes tcb25-life {
from {
transform: scaleX(1);
}
to {
transform: scaleX(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. */
.tcb-25 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const toast = document.getElementById('tcb-25-toast');
if (!toast || toast.dataset.wired) return;
toast.dataset.wired = '1';
const bar = document.getElementById('tcb-25-bar');
const reset = document.getElementById('tcb-25-reset');
const leave = () => {
if (toast.hasAttribute('data-leaving')) return;
toast.setAttribute('data-leaving', '');
setTimeout(() => { toast.hidden = true; reset.hidden = false; reset.focus(); }, 300);
};
bar.addEventListener('animationend', leave);
document.getElementById('tcb-25-close').addEventListener('click', leave);
reset.addEventListener('click', () => {
toast.hidden = false;
toast.removeAttribute('data-leaving');
reset.hidden = true;
bar.style.animation = 'none';
requestAnimationFrame(() => { bar.style.animation = ''; });
});
})();
```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 Toast Notification Dismiss Button
Source: https://codefronts.com/components/tailwind-css-close-buttons/toast-dismiss/
A success toast with a draining countdown bar that owns the clock: animate-tcb25-life shrinks it over 5s and animationend dismisses the toast — no duplicate timer. Hover or keyboard focus pauses it with group-hover/toast:[animation-play-state:paused], satisfying WCAG 2.2.1 (Timing Adjustable). The × is a 40px padded target pulled into the corner with negative margins.
## HTML
```html
<section class="tcb-25 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tcb25-bg),var(--color-tcb25-bg2))] px-6 py-12 font-tcb25-sans" aria-label="Toast notification dismiss button demo">
<div class="grid w-full justify-items-center gap-7">
<div class="grid w-full max-w-[23rem] justify-items-center gap-4">
<div id="tcb-25-toast" role="status" class="group/toast relative flex w-full items-center gap-3 overflow-hidden rounded-[14px] border border-tcb25-line/60 bg-tcb25-card py-3.5 ps-4 pe-3 shadow-[0_20px_46px_-24px_rgb(0_0_0/0.85)] transition duration-300 data-leaving:translate-x-6 data-leaving:opacity-0 motion-reduce:transition-none">
<span aria-hidden="true" class="grid size-7 flex-none place-items-center rounded-full bg-tcb25-edge/25 text-tcb25-edge"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m3.5 8.5 3 3 6-7"/></svg></span>
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
<strong class="text-[13.5px] font-semibold text-tcb25-ink">Changes saved</strong>
<span class="text-xs text-tcb25-mut">Hover or focus to pause the countdown</span>
</div>
<button id="tcb-25-close" type="button" aria-label="Dismiss notification" class="relative -my-2 grid size-11 flex-none cursor-pointer place-items-center rounded-[10px] transition-colors hover:bg-white/8 focus-visible:outline-[2.5px] focus-visible:-outline-offset-2 focus-visible:outline-tcb25-edge motion-reduce:transition-none">
<span aria-hidden="true" class="relative block size-4 before:absolute before:top-1/2 before:left-1/2 before:h-0.5 before:w-4 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-tcb25-x after:absolute after:top-1/2 after:left-1/2 after:h-0.5 after:w-4 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-tcb25-x"></span>
</button>
<span id="tcb-25-bar" aria-hidden="true" class="absolute inset-x-0 bottom-0 h-[3px] origin-left bg-tcb25-edge animate-tcb25-life group-hover/toast:[animation-play-state:paused] group-focus-within/toast:[animation-play-state:paused] motion-reduce:[animation-duration:20s]"></span>
</div>
<button id="tcb-25-reset" type="button" hidden class="min-h-11 cursor-pointer px-2 text-[13px] font-medium text-tcb25-mut underline decoration-dashed underline-offset-4 hover:text-tcb25-ink focus-visible:outline-2 focus-visible:outline-tcb25-edge">Show toast again</button>
</div>
<p aria-hidden="true" class="rounded-full px-3.5 py-2 font-tcb25-mono text-[11.5px] text-tcb25-mut ring-1 ring-white/12">animate-tcb25-life · group-focus-within/toast:[animation-play-state:paused]</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcb25-bg: #1e2430;
--color-tcb25-bg: oklch(0.23 0.02 250);
--color-tcb25-bg2: #141922;
--color-tcb25-bg2: oklch(0.17 0.015 250);
--color-tcb25-card: #2a303c;
--color-tcb25-card: oklch(0.28 0.02 250);
--color-tcb25-line: #4a5160;
--color-tcb25-line: oklch(0.4 0.02 250);
--color-tcb25-ink: #eceef3;
--color-tcb25-ink: oklch(0.94 0.01 250);
--color-tcb25-mut: #a9afbb;
--color-tcb25-mut: oklch(0.75 0.02 250);
--color-tcb25-x: #c3c8d2;
--color-tcb25-x: oklch(0.83 0.02 250);
--color-tcb25-edge: #3fcf94;
--color-tcb25-edge: oklch(0.76 0.15 160);
--font-tcb25-sans: 'Geist', system-ui, sans-serif;
--font-tcb25-mono: 'Geist Mono', ui-monospace, monospace;
--animate-tcb25-life: tcb25-life 5s linear forwards;
@keyframes tcb25-life {
from {
transform: scaleX(1);
}
to {
transform: scaleX(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. */
.tcb-25 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const toast = document.getElementById('tcb-25-toast');
if (!toast || toast.dataset.wired) return;
toast.dataset.wired = '1';
const bar = document.getElementById('tcb-25-bar');
const reset = document.getElementById('tcb-25-reset');
const leave = () => {
if (toast.hasAttribute('data-leaving')) return;
toast.setAttribute('data-leaving', '');
setTimeout(() => { toast.hidden = true; reset.hidden = false; reset.focus(); }, 300);
};
bar.addEventListener('animationend', leave);
document.getElementById('tcb-25-close').addEventListener('click', leave);
reset.addEventListener('click', () => {
toast.hidden = false;
toast.removeAttribute('data-leaving');
reset.hidden = true;
bar.style.animation = 'none';
requestAnimationFrame(() => { bar.style.animation = ''; });
});
})();
```How this works
The animation is the timer. Pausing the bar pauses dismissal automatically, because dismissal listens for the bar's animationend:
bar.addEventListener('animationend', leave);
<span class="… origin-left animate-tcb25-life
group-hover/toast:[animation-play-state:paused]
group-focus-within/toast:[animation-play-state:paused]">Restarting for the demo sets animation: none, waits a frame and clears it.
Make it yours
- Lifetime: change
5sin--animate-tcb25-life. - Error toast: swap
--color-tcb25-edgefor a red and the icon path for an exclamation. - Stack: render toasts in a
fixed bottom-4 end-4 grid gap-2region witharia-live="polite".
Gotchas — read before shipping
role="status"must be on the toast (or a persistent live region) before content changes for announcements to fire.- Don't auto-dismiss toasts that contain actions users need time to reach.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
The floor is Tailwind v4's generated sheet.