05 / 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 Cookie Consent Banner Dismiss Button
A GDPR-style consent bar whose Dismiss pill carries a CSS-drawn × after its label. data-dismissed:translate-y-[120%] data-dismissed:opacity-0 data-dismissed:invisible slides it away; inert removes it from the tab order the instant it leaves. The port also fixes the original's dead "Accept all" button — both choices now close the banner and announce what happened.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tcb-05 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tcb05-bg),var(--color-tcb05-bg2))] px-6 py-12 font-tcb05-sans" aria-label="Cookie banner dismiss button demo">
<div class="grid w-full justify-items-center gap-7">
<div class="grid w-full max-w-[34rem] justify-items-center gap-4">
<div id="tcb-05-banner" role="region" aria-label="Cookie consent" class="flex w-full flex-wrap items-center gap-x-6 gap-y-4 rounded-[1.25rem] bg-tcb05-surface px-6 py-5 text-tcb05-ink shadow-[0_24px_54px_-26px_var(--color-tcb05-shadow)] ring-1 ring-white/8 transition-[translate,opacity,visibility] duration-350 data-dismissed:invisible data-dismissed:translate-y-[120%] data-dismissed:opacity-0 motion-reduce:transition-none">
<div class="flex flex-1 basis-56 items-start gap-3">
<span aria-hidden="true" class="mt-0.5 grid size-8 flex-none place-items-center rounded-full bg-tcb05-acc/15 text-tcb05-acc"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 3a9 9 0 1 0 9 9 3 3 0 0 1-3-3 3 3 0 0 1-3-3 3 3 0 0 1-3-3Z"/><circle cx="9" cy="13" r=".8" fill="currentColor"/><circle cx="14" cy="16" r=".8" fill="currentColor"/></svg></span>
<p class="text-[13.5px]/[1.55] text-tcb05-ink/90 text-pretty">We use cookies to improve your experience and analyse traffic. <a href="#" class="font-medium text-tcb05-ink underline decoration-tcb05-acc underline-offset-3 hover:decoration-2 focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc">Policy</a></p>
</div>
<div class="flex gap-2.5">
<button type="button" data-choice="accepted" class="inline-flex min-h-11 cursor-pointer items-center rounded-full border-[1.5px] border-tcb05-acc bg-tcb05-acc px-4.5 text-[13.5px] font-semibold text-tcb05-on transition duration-150 hover:-translate-y-px hover:shadow-[0_8px_18px_-8px_rgb(0_0_0/0.5)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc active:translate-y-0 active:scale-97 motion-reduce:transition-none">Accept all</button>
<button type="button" data-choice="essential" class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-tcb05-ink/45 px-4.5 text-[13.5px] font-semibold transition duration-150 hover:-translate-y-px hover:border-tcb05-ink/70 hover:shadow-[0_8px_18px_-8px_rgb(0_0_0/0.5)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc active:translate-y-0 active:scale-97 motion-reduce:transition-none">
Dismiss
<span aria-hidden="true" class="relative size-2.5 before:absolute before:top-1/2 before:left-1/2 before:h-[1.8px] before:w-3 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-current after:absolute after:top-1/2 after:left-1/2 after:h-[1.8px] after:w-3 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-current"></span>
</button>
</div>
</div>
<p id="tcb-05-status" role="status" class="min-h-5 text-[13px] text-tcb05-mut"></p>
<button id="tcb-05-reset" type="button" hidden class="min-h-11 cursor-pointer px-2 text-[13px] font-medium text-tcb05-mut underline decoration-dashed underline-offset-4 hover:text-tcb05-surface focus-visible:outline-2 focus-visible:outline-tcb05-surface">Show banner again</button>
</div>
<p aria-hidden="true" class="rounded-full bg-white/70 px-3.5 py-2 font-tcb05-mono text-[11.5px] text-tcb05-mut ring-1 ring-black/8">data-dismissed: · invisible · inert</p>
</div>
</section><section class="tcb-05 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tcb05-bg),var(--color-tcb05-bg2))] px-6 py-12 font-tcb05-sans" aria-label="Cookie banner dismiss button demo">
<div class="grid w-full justify-items-center gap-7">
<div class="grid w-full max-w-[34rem] justify-items-center gap-4">
<div id="tcb-05-banner" role="region" aria-label="Cookie consent" class="flex w-full flex-wrap items-center gap-x-6 gap-y-4 rounded-[1.25rem] bg-tcb05-surface px-6 py-5 text-tcb05-ink shadow-[0_24px_54px_-26px_var(--color-tcb05-shadow)] ring-1 ring-white/8 transition-[translate,opacity,visibility] duration-350 data-dismissed:invisible data-dismissed:translate-y-[120%] data-dismissed:opacity-0 motion-reduce:transition-none">
<div class="flex flex-1 basis-56 items-start gap-3">
<span aria-hidden="true" class="mt-0.5 grid size-8 flex-none place-items-center rounded-full bg-tcb05-acc/15 text-tcb05-acc"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 3a9 9 0 1 0 9 9 3 3 0 0 1-3-3 3 3 0 0 1-3-3 3 3 0 0 1-3-3Z"/><circle cx="9" cy="13" r=".8" fill="currentColor"/><circle cx="14" cy="16" r=".8" fill="currentColor"/></svg></span>
<p class="text-[13.5px]/[1.55] text-tcb05-ink/90 text-pretty">We use cookies to improve your experience and analyse traffic. <a href="#" class="font-medium text-tcb05-ink underline decoration-tcb05-acc underline-offset-3 hover:decoration-2 focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc">Policy</a></p>
</div>
<div class="flex gap-2.5">
<button type="button" data-choice="accepted" class="inline-flex min-h-11 cursor-pointer items-center rounded-full border-[1.5px] border-tcb05-acc bg-tcb05-acc px-4.5 text-[13.5px] font-semibold text-tcb05-on transition duration-150 hover:-translate-y-px hover:shadow-[0_8px_18px_-8px_rgb(0_0_0/0.5)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc active:translate-y-0 active:scale-97 motion-reduce:transition-none">Accept all</button>
<button type="button" data-choice="essential" class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-tcb05-ink/45 px-4.5 text-[13.5px] font-semibold transition duration-150 hover:-translate-y-px hover:border-tcb05-ink/70 hover:shadow-[0_8px_18px_-8px_rgb(0_0_0/0.5)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc active:translate-y-0 active:scale-97 motion-reduce:transition-none">
Dismiss
<span aria-hidden="true" class="relative size-2.5 before:absolute before:top-1/2 before:left-1/2 before:h-[1.8px] before:w-3 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-current after:absolute after:top-1/2 after:left-1/2 after:h-[1.8px] after:w-3 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-current"></span>
</button>
</div>
</div>
<p id="tcb-05-status" role="status" class="min-h-5 text-[13px] text-tcb05-mut"></p>
<button id="tcb-05-reset" type="button" hidden class="min-h-11 cursor-pointer px-2 text-[13px] font-medium text-tcb05-mut underline decoration-dashed underline-offset-4 hover:text-tcb05-surface focus-visible:outline-2 focus-visible:outline-tcb05-surface">Show banner again</button>
</div>
<p aria-hidden="true" class="rounded-full bg-white/70 px-3.5 py-2 font-tcb05-mono text-[11.5px] text-tcb05-mut ring-1 ring-black/8">data-dismissed: · invisible · inert</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-tcb05-bg: #f1f6f7;
--color-tcb05-bg: oklch(0.96 0.008 200);
--color-tcb05-bg2: #e1eaed;
--color-tcb05-bg2: oklch(0.92 0.015 210);
--color-tcb05-surface: #1f2230;
--color-tcb05-surface: oklch(0.23 0.02 270);
--color-tcb05-ink: #e8e9f0;
--color-tcb05-ink: oklch(0.93 0.01 270);
--color-tcb05-mut: #3f5761;
--color-tcb05-mut: oklch(0.42 0.04 220);
--color-tcb05-acc: #4fd4a2;
--color-tcb05-acc: oklch(0.78 0.14 160);
--color-tcb05-on: #0d2a1f;
--color-tcb05-on: oklch(0.22 0.04 160);
--color-tcb05-shadow: #22253ab3;
--color-tcb05-shadow: oklch(0.3 0.04 270 / 0.7);
--font-tcb05-sans: 'Geist', system-ui, sans-serif;
--font-tcb05-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-05 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tcb05-bg: #f1f6f7;
--color-tcb05-bg: oklch(0.96 0.008 200);
--color-tcb05-bg2: #e1eaed;
--color-tcb05-bg2: oklch(0.92 0.015 210);
--color-tcb05-surface: #1f2230;
--color-tcb05-surface: oklch(0.23 0.02 270);
--color-tcb05-ink: #e8e9f0;
--color-tcb05-ink: oklch(0.93 0.01 270);
--color-tcb05-mut: #3f5761;
--color-tcb05-mut: oklch(0.42 0.04 220);
--color-tcb05-acc: #4fd4a2;
--color-tcb05-acc: oklch(0.78 0.14 160);
--color-tcb05-on: #0d2a1f;
--color-tcb05-on: oklch(0.22 0.04 160);
--color-tcb05-shadow: #22253ab3;
--color-tcb05-shadow: oklch(0.3 0.04 270 / 0.7);
--font-tcb05-sans: 'Geist', system-ui, sans-serif;
--font-tcb05-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-05 {
width: 100%;
min-height: 100svh;
}(() => {
const banner = document.getElementById('tcb-05-banner');
if (!banner || banner.dataset.wired) return;
banner.dataset.wired = '1';
const reset = document.getElementById('tcb-05-reset');
const status = document.getElementById('tcb-05-status');
banner.querySelectorAll('[data-choice]').forEach((btn) => {
btn.addEventListener('click', () => {
const choice = btn.dataset.choice;
banner.setAttribute('data-dismissed', '');
banner.inert = true;
status.textContent = choice === 'accepted' ? 'All cookies accepted.' : 'Only essential cookies will be used.';
// Production: localStorage.setItem('consent', choice);
reset.hidden = false;
reset.focus();
});
});
reset.addEventListener('click', () => {
banner.removeAttribute('data-dismissed');
banner.inert = false;
status.textContent = '';
reset.hidden = true;
});
})();(() => {
const banner = document.getElementById('tcb-05-banner');
if (!banner || banner.dataset.wired) return;
banner.dataset.wired = '1';
const reset = document.getElementById('tcb-05-reset');
const status = document.getElementById('tcb-05-status');
banner.querySelectorAll('[data-choice]').forEach((btn) => {
btn.addEventListener('click', () => {
const choice = btn.dataset.choice;
banner.setAttribute('data-dismissed', '');
banner.inert = true;
status.textContent = choice === 'accepted' ? 'All cookies accepted.' : 'Only essential cookies will be used.';
// Production: localStorage.setItem('consent', choice);
reset.hidden = false;
reset.focus();
});
});
reset.addEventListener('click', () => {
banner.removeAttribute('data-dismissed');
banner.inert = false;
status.textContent = '';
reset.hidden = true;
});
})();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-tcb05-bg: #f1f6f7;
--color-tcb05-bg: oklch(0.96 0.008 200);
--color-tcb05-bg2: #e1eaed;
--color-tcb05-bg2: oklch(0.92 0.015 210);
--color-tcb05-surface: #1f2230;
--color-tcb05-surface: oklch(0.23 0.02 270);
--color-tcb05-ink: #e8e9f0;
--color-tcb05-ink: oklch(0.93 0.01 270);
--color-tcb05-mut: #3f5761;
--color-tcb05-mut: oklch(0.42 0.04 220);
--color-tcb05-acc: #4fd4a2;
--color-tcb05-acc: oklch(0.78 0.14 160);
--color-tcb05-on: #0d2a1f;
--color-tcb05-on: oklch(0.22 0.04 160);
--color-tcb05-shadow: #22253ab3;
--color-tcb05-shadow: oklch(0.3 0.04 270 / 0.7);
--font-tcb05-sans: 'Geist', system-ui, sans-serif;
--font-tcb05-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-05 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tcb05-bg: #f1f6f7;
--color-tcb05-bg: oklch(0.96 0.008 200);
--color-tcb05-bg2: #e1eaed;
--color-tcb05-bg2: oklch(0.92 0.015 210);
--color-tcb05-surface: #1f2230;
--color-tcb05-surface: oklch(0.23 0.02 270);
--color-tcb05-ink: #e8e9f0;
--color-tcb05-ink: oklch(0.93 0.01 270);
--color-tcb05-mut: #3f5761;
--color-tcb05-mut: oklch(0.42 0.04 220);
--color-tcb05-acc: #4fd4a2;
--color-tcb05-acc: oklch(0.78 0.14 160);
--color-tcb05-on: #0d2a1f;
--color-tcb05-on: oklch(0.22 0.04 160);
--color-tcb05-shadow: #22253ab3;
--color-tcb05-shadow: oklch(0.3 0.04 270 / 0.7);
--font-tcb05-sans: 'Geist', system-ui, sans-serif;
--font-tcb05-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-05 {
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-dismissedplus the 350msvisibilitysetTimeoutbecame threedata-dismissed:utilities and avisibilitytransition — no timers left.- Bug fix: "Accept all" did nothing in the CSS original; both choices now close the banner and a live region confirms the choice.
- UI upgrade from the CSS original: buttons grew to 44px tall (were 37px), the banner gained a cookie glyph and an inline policy link, and the dismissed banner is
inertso its buttons cannot be tabbed to while off-screen. - UI upgrade: the reset link's colour moved from
oklch(0.45 0.05 210)to0.42 0.04 220for 7:1 on the pale stage.
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 Cookie Consent Banner Dismiss Button
Source: https://codefronts.com/components/tailwind-css-close-buttons/tailwind-cookie-consent-banner-dismiss-button/
A GDPR-style consent bar whose Dismiss pill carries a CSS-drawn × after its label. data-dismissed:translate-y-[120%] data-dismissed:opacity-0 data-dismissed:invisible slides it away; inert removes it from the tab order the instant it leaves. The port also fixes the original's dead "Accept all" button — both choices now close the banner and announce what happened.
## HTML
```html
<section class="tcb-05 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tcb05-bg),var(--color-tcb05-bg2))] px-6 py-12 font-tcb05-sans" aria-label="Cookie banner dismiss button demo">
<div class="grid w-full justify-items-center gap-7">
<div class="grid w-full max-w-[34rem] justify-items-center gap-4">
<div id="tcb-05-banner" role="region" aria-label="Cookie consent" class="flex w-full flex-wrap items-center gap-x-6 gap-y-4 rounded-[1.25rem] bg-tcb05-surface px-6 py-5 text-tcb05-ink shadow-[0_24px_54px_-26px_var(--color-tcb05-shadow)] ring-1 ring-white/8 transition-[translate,opacity,visibility] duration-350 data-dismissed:invisible data-dismissed:translate-y-[120%] data-dismissed:opacity-0 motion-reduce:transition-none">
<div class="flex flex-1 basis-56 items-start gap-3">
<span aria-hidden="true" class="mt-0.5 grid size-8 flex-none place-items-center rounded-full bg-tcb05-acc/15 text-tcb05-acc"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 3a9 9 0 1 0 9 9 3 3 0 0 1-3-3 3 3 0 0 1-3-3 3 3 0 0 1-3-3Z"/><circle cx="9" cy="13" r=".8" fill="currentColor"/><circle cx="14" cy="16" r=".8" fill="currentColor"/></svg></span>
<p class="text-[13.5px]/[1.55] text-tcb05-ink/90 text-pretty">We use cookies to improve your experience and analyse traffic. <a href="#" class="font-medium text-tcb05-ink underline decoration-tcb05-acc underline-offset-3 hover:decoration-2 focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc">Policy</a></p>
</div>
<div class="flex gap-2.5">
<button type="button" data-choice="accepted" class="inline-flex min-h-11 cursor-pointer items-center rounded-full border-[1.5px] border-tcb05-acc bg-tcb05-acc px-4.5 text-[13.5px] font-semibold text-tcb05-on transition duration-150 hover:-translate-y-px hover:shadow-[0_8px_18px_-8px_rgb(0_0_0/0.5)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc active:translate-y-0 active:scale-97 motion-reduce:transition-none">Accept all</button>
<button type="button" data-choice="essential" class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-tcb05-ink/45 px-4.5 text-[13.5px] font-semibold transition duration-150 hover:-translate-y-px hover:border-tcb05-ink/70 hover:shadow-[0_8px_18px_-8px_rgb(0_0_0/0.5)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc active:translate-y-0 active:scale-97 motion-reduce:transition-none">
Dismiss
<span aria-hidden="true" class="relative size-2.5 before:absolute before:top-1/2 before:left-1/2 before:h-[1.8px] before:w-3 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-current after:absolute after:top-1/2 after:left-1/2 after:h-[1.8px] after:w-3 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-current"></span>
</button>
</div>
</div>
<p id="tcb-05-status" role="status" class="min-h-5 text-[13px] text-tcb05-mut"></p>
<button id="tcb-05-reset" type="button" hidden class="min-h-11 cursor-pointer px-2 text-[13px] font-medium text-tcb05-mut underline decoration-dashed underline-offset-4 hover:text-tcb05-surface focus-visible:outline-2 focus-visible:outline-tcb05-surface">Show banner again</button>
</div>
<p aria-hidden="true" class="rounded-full bg-white/70 px-3.5 py-2 font-tcb05-mono text-[11.5px] text-tcb05-mut ring-1 ring-black/8">data-dismissed: · invisible · inert</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcb05-bg: #f1f6f7;
--color-tcb05-bg: oklch(0.96 0.008 200);
--color-tcb05-bg2: #e1eaed;
--color-tcb05-bg2: oklch(0.92 0.015 210);
--color-tcb05-surface: #1f2230;
--color-tcb05-surface: oklch(0.23 0.02 270);
--color-tcb05-ink: #e8e9f0;
--color-tcb05-ink: oklch(0.93 0.01 270);
--color-tcb05-mut: #3f5761;
--color-tcb05-mut: oklch(0.42 0.04 220);
--color-tcb05-acc: #4fd4a2;
--color-tcb05-acc: oklch(0.78 0.14 160);
--color-tcb05-on: #0d2a1f;
--color-tcb05-on: oklch(0.22 0.04 160);
--color-tcb05-shadow: #22253ab3;
--color-tcb05-shadow: oklch(0.3 0.04 270 / 0.7);
--font-tcb05-sans: 'Geist', system-ui, sans-serif;
--font-tcb05-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-05 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const banner = document.getElementById('tcb-05-banner');
if (!banner || banner.dataset.wired) return;
banner.dataset.wired = '1';
const reset = document.getElementById('tcb-05-reset');
const status = document.getElementById('tcb-05-status');
banner.querySelectorAll('[data-choice]').forEach((btn) => {
btn.addEventListener('click', () => {
const choice = btn.dataset.choice;
banner.setAttribute('data-dismissed', '');
banner.inert = true;
status.textContent = choice === 'accepted' ? 'All cookies accepted.' : 'Only essential cookies will be used.';
// Production: localStorage.setItem('consent', choice);
reset.hidden = false;
reset.focus();
});
});
reset.addEventListener('click', () => {
banner.removeAttribute('data-dismissed');
banner.inert = false;
status.textContent = '';
reset.hidden = true;
});
})();
```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 Cookie Consent Banner Dismiss Button
Source: https://codefronts.com/components/tailwind-css-close-buttons/tailwind-cookie-consent-banner-dismiss-button/
A GDPR-style consent bar whose Dismiss pill carries a CSS-drawn × after its label. data-dismissed:translate-y-[120%] data-dismissed:opacity-0 data-dismissed:invisible slides it away; inert removes it from the tab order the instant it leaves. The port also fixes the original's dead "Accept all" button — both choices now close the banner and announce what happened.
## HTML
```html
<section class="tcb-05 w-full min-h-svh grid place-items-center bg-[linear-gradient(180deg,var(--color-tcb05-bg),var(--color-tcb05-bg2))] px-6 py-12 font-tcb05-sans" aria-label="Cookie banner dismiss button demo">
<div class="grid w-full justify-items-center gap-7">
<div class="grid w-full max-w-[34rem] justify-items-center gap-4">
<div id="tcb-05-banner" role="region" aria-label="Cookie consent" class="flex w-full flex-wrap items-center gap-x-6 gap-y-4 rounded-[1.25rem] bg-tcb05-surface px-6 py-5 text-tcb05-ink shadow-[0_24px_54px_-26px_var(--color-tcb05-shadow)] ring-1 ring-white/8 transition-[translate,opacity,visibility] duration-350 data-dismissed:invisible data-dismissed:translate-y-[120%] data-dismissed:opacity-0 motion-reduce:transition-none">
<div class="flex flex-1 basis-56 items-start gap-3">
<span aria-hidden="true" class="mt-0.5 grid size-8 flex-none place-items-center rounded-full bg-tcb05-acc/15 text-tcb05-acc"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 3a9 9 0 1 0 9 9 3 3 0 0 1-3-3 3 3 0 0 1-3-3 3 3 0 0 1-3-3Z"/><circle cx="9" cy="13" r=".8" fill="currentColor"/><circle cx="14" cy="16" r=".8" fill="currentColor"/></svg></span>
<p class="text-[13.5px]/[1.55] text-tcb05-ink/90 text-pretty">We use cookies to improve your experience and analyse traffic. <a href="#" class="font-medium text-tcb05-ink underline decoration-tcb05-acc underline-offset-3 hover:decoration-2 focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc">Policy</a></p>
</div>
<div class="flex gap-2.5">
<button type="button" data-choice="accepted" class="inline-flex min-h-11 cursor-pointer items-center rounded-full border-[1.5px] border-tcb05-acc bg-tcb05-acc px-4.5 text-[13.5px] font-semibold text-tcb05-on transition duration-150 hover:-translate-y-px hover:shadow-[0_8px_18px_-8px_rgb(0_0_0/0.5)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc active:translate-y-0 active:scale-97 motion-reduce:transition-none">Accept all</button>
<button type="button" data-choice="essential" class="inline-flex min-h-11 cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-tcb05-ink/45 px-4.5 text-[13.5px] font-semibold transition duration-150 hover:-translate-y-px hover:border-tcb05-ink/70 hover:shadow-[0_8px_18px_-8px_rgb(0_0_0/0.5)] focus-visible:outline-[2.5px] focus-visible:outline-offset-2 focus-visible:outline-tcb05-acc active:translate-y-0 active:scale-97 motion-reduce:transition-none">
Dismiss
<span aria-hidden="true" class="relative size-2.5 before:absolute before:top-1/2 before:left-1/2 before:h-[1.8px] before:w-3 before:-translate-1/2 before:rotate-45 before:rounded-full before:bg-current after:absolute after:top-1/2 after:left-1/2 after:h-[1.8px] after:w-3 after:-translate-1/2 after:-rotate-45 after:rounded-full after:bg-current"></span>
</button>
</div>
</div>
<p id="tcb-05-status" role="status" class="min-h-5 text-[13px] text-tcb05-mut"></p>
<button id="tcb-05-reset" type="button" hidden class="min-h-11 cursor-pointer px-2 text-[13px] font-medium text-tcb05-mut underline decoration-dashed underline-offset-4 hover:text-tcb05-surface focus-visible:outline-2 focus-visible:outline-tcb05-surface">Show banner again</button>
</div>
<p aria-hidden="true" class="rounded-full bg-white/70 px-3.5 py-2 font-tcb05-mono text-[11.5px] text-tcb05-mut ring-1 ring-black/8">data-dismissed: · invisible · inert</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcb05-bg: #f1f6f7;
--color-tcb05-bg: oklch(0.96 0.008 200);
--color-tcb05-bg2: #e1eaed;
--color-tcb05-bg2: oklch(0.92 0.015 210);
--color-tcb05-surface: #1f2230;
--color-tcb05-surface: oklch(0.23 0.02 270);
--color-tcb05-ink: #e8e9f0;
--color-tcb05-ink: oklch(0.93 0.01 270);
--color-tcb05-mut: #3f5761;
--color-tcb05-mut: oklch(0.42 0.04 220);
--color-tcb05-acc: #4fd4a2;
--color-tcb05-acc: oklch(0.78 0.14 160);
--color-tcb05-on: #0d2a1f;
--color-tcb05-on: oklch(0.22 0.04 160);
--color-tcb05-shadow: #22253ab3;
--color-tcb05-shadow: oklch(0.3 0.04 270 / 0.7);
--font-tcb05-sans: 'Geist', system-ui, sans-serif;
--font-tcb05-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-05 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const banner = document.getElementById('tcb-05-banner');
if (!banner || banner.dataset.wired) return;
banner.dataset.wired = '1';
const reset = document.getElementById('tcb-05-reset');
const status = document.getElementById('tcb-05-status');
banner.querySelectorAll('[data-choice]').forEach((btn) => {
btn.addEventListener('click', () => {
const choice = btn.dataset.choice;
banner.setAttribute('data-dismissed', '');
banner.inert = true;
status.textContent = choice === 'accepted' ? 'All cookies accepted.' : 'Only essential cookies will be used.';
// Production: localStorage.setItem('consent', choice);
reset.hidden = false;
reset.focus();
});
});
reset.addEventListener('click', () => {
banner.removeAttribute('data-dismissed');
banner.inert = false;
status.textContent = '';
reset.hidden = true;
});
})();
```How this works
The attribute is the state. JS sets data-dismissed; the banner's own class list describes what that looks like:
transition-[translate,opacity,visibility] duration-350
data-dismissed:translate-y-[120%] data-dismissed:opacity-0 data-dismissed:invisibleFocus goes somewhere sensible. When the banner leaves, focus moves to the "Show banner again" link (in production: the next focusable element), so keyboard users are never stranded on a hidden button.
Make it yours
- Persist: replace the demo's reset with
localStorage.setItem('consent', choice)and skip rendering the banner when it is set. - Pin it: wrap the banner in
fixed inset-x-4 bottom-4 mx-autofor a real page. - Light theme: flip
bg-tcb05-surfacetobg-whiteandtext-tcb05-inkto a dark token.
Gotchas — read before shipping
- An unchosen consent banner must not be dismissed as "accepted" — keep Dismiss meaning "essential only" and say so in your policy.
invisiblealone does not remove the element from the accessibility tree in every browser;inertdoes.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
The floor is Tailwind v4; inert and visibility transitions are universal on that floor.