16 / 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
Glitch Effect Tailwind Close Button
RGB-split glitch on a cyber-terminal button: the × is painted from two gradient bars stored in one arbitrary custom property, and two tinted ghost copies — cyan and magenta via before:text-* and currentColor — jitter on desynced steps() tracks while clip-path: inset() tears them into scanlines. Keyframes live in @theme; the rest is markup.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tcb-16 w-full min-h-svh grid place-items-center bg-tcb16-bg bg-[radial-gradient(100%_110%_at_50%_0%,var(--color-tcb16-glow),transparent_60%)] px-6 py-12 font-tcb16-mono" aria-label="Glitch close button demo">
<div class="grid w-full justify-items-center gap-7">
<button type="button" aria-label="Close" class="group isolate grid size-18 cursor-pointer place-items-center rounded-xl border border-tcb16-c1/35 bg-tcb16-btn shadow-[0_0_0_1px_rgb(0_0_0/0.6),0_18px_40px_-20px_rgb(0_0_0/0.9)] transition-colors duration-200 hover:border-tcb16-c1 focus-visible:border-tcb16-c1 focus-visible:outline-[2.5px] focus-visible:outline-offset-3 focus-visible:outline-tcb16-c1 active:scale-93 motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-6.5 bg-(image:--tcb16-bars) text-white [--tcb16-bars:linear-gradient(45deg,transparent_calc(50%_-_1.25px),currentColor_calc(50%_-_1.25px)_calc(50%_+_1.25px),transparent_calc(50%_+_1.25px)),linear-gradient(-45deg,transparent_calc(50%_-_1.25px),currentColor_calc(50%_-_1.25px)_calc(50%_+_1.25px),transparent_calc(50%_+_1.25px))] before:absolute before:inset-0 before:bg-(image:--tcb16-bars) before:text-tcb16-c1 before:opacity-0 before:mix-blend-screen after:absolute after:inset-0 after:bg-(image:--tcb16-bars) after:text-tcb16-c2 after:opacity-0 after:mix-blend-screen group-hover:before:animate-tcb16-g1 group-hover:before:opacity-100 group-hover:after:animate-tcb16-g2 group-hover:after:opacity-100 group-focus-visible:before:animate-tcb16-g1 group-focus-visible:before:opacity-100 group-focus-visible:after:animate-tcb16-g2 group-focus-visible:after:opacity-100 motion-reduce:before:hidden motion-reduce:after:hidden"></span>
</button>
<p aria-hidden="true" class="rounded-md border border-tcb16-c1/25 px-3.5 py-2 text-[11.5px] text-tcb16-mut">bg-(image:--tcb16-bars) · before:mix-blend-screen · steps(2,jump-none)</p>
</div>
</section><section class="tcb-16 w-full min-h-svh grid place-items-center bg-tcb16-bg bg-[radial-gradient(100%_110%_at_50%_0%,var(--color-tcb16-glow),transparent_60%)] px-6 py-12 font-tcb16-mono" aria-label="Glitch close button demo">
<div class="grid w-full justify-items-center gap-7">
<button type="button" aria-label="Close" class="group isolate grid size-18 cursor-pointer place-items-center rounded-xl border border-tcb16-c1/35 bg-tcb16-btn shadow-[0_0_0_1px_rgb(0_0_0/0.6),0_18px_40px_-20px_rgb(0_0_0/0.9)] transition-colors duration-200 hover:border-tcb16-c1 focus-visible:border-tcb16-c1 focus-visible:outline-[2.5px] focus-visible:outline-offset-3 focus-visible:outline-tcb16-c1 active:scale-93 motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-6.5 bg-(image:--tcb16-bars) text-white [--tcb16-bars:linear-gradient(45deg,transparent_calc(50%_-_1.25px),currentColor_calc(50%_-_1.25px)_calc(50%_+_1.25px),transparent_calc(50%_+_1.25px)),linear-gradient(-45deg,transparent_calc(50%_-_1.25px),currentColor_calc(50%_-_1.25px)_calc(50%_+_1.25px),transparent_calc(50%_+_1.25px))] before:absolute before:inset-0 before:bg-(image:--tcb16-bars) before:text-tcb16-c1 before:opacity-0 before:mix-blend-screen after:absolute after:inset-0 after:bg-(image:--tcb16-bars) after:text-tcb16-c2 after:opacity-0 after:mix-blend-screen group-hover:before:animate-tcb16-g1 group-hover:before:opacity-100 group-hover:after:animate-tcb16-g2 group-hover:after:opacity-100 group-focus-visible:before:animate-tcb16-g1 group-focus-visible:before:opacity-100 group-focus-visible:after:animate-tcb16-g2 group-focus-visible:after:opacity-100 motion-reduce:before:hidden motion-reduce:after:hidden"></span>
</button>
<p aria-hidden="true" class="rounded-md border border-tcb16-c1/25 px-3.5 py-2 text-[11.5px] text-tcb16-mut">bg-(image:--tcb16-bars) · before:mix-blend-screen · steps(2,jump-none)</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-tcb16-bg: #0b0a14;
--color-tcb16-bg: oklch(0.13 0.015 280);
--color-tcb16-glow: #1c1a2ee6;
--color-tcb16-glow: oklch(0.22 0.03 280 / 0.9);
--color-tcb16-btn: #13121e;
--color-tcb16-btn: oklch(0.17 0.02 280);
--color-tcb16-c1: #3de6f2;
--color-tcb16-c1: oklch(0.85 0.15 195);
--color-tcb16-c2: #f0369b;
--color-tcb16-c2: oklch(0.65 0.26 345);
--color-tcb16-mut: #9cc9cf;
--color-tcb16-mut: oklch(0.8 0.05 200);
--font-tcb16-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tcb16-g1: tcb16-g1 0.32s steps(2, jump-none) infinite;
--animate-tcb16-g2: tcb16-g2 0.44s steps(2, jump-none) infinite;
@keyframes tcb16-g1 {
0% {
transform: translateX(-3px);
clip-path: inset(0 0 62% 0);
}
23% {
transform: translateX(2px);
clip-path: inset(38% 0 21% 0);
}
41% {
transform: translateX(-2px);
clip-path: inset(71% 0 0 0);
}
67% {
transform: translateX(3px);
clip-path: inset(12% 0 55% 0);
}
100% {
transform: translateX(-1px);
clip-path: inset(45% 0 32% 0);
}
}
@keyframes tcb16-g2 {
0% {
transform: translateX(3px);
clip-path: inset(58% 0 8% 0);
}
19% {
transform: translateX(-2px);
clip-path: inset(9% 0 70% 0);
}
47% {
transform: translateX(2px);
clip-path: inset(33% 0 40% 0);
}
73% {
transform: translateX(-3px);
clip-path: inset(80% 0 2% 0);
}
100% {
transform: translateX(1px);
clip-path: inset(21% 0 48% 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-16 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tcb16-bg: #0b0a14;
--color-tcb16-bg: oklch(0.13 0.015 280);
--color-tcb16-glow: #1c1a2ee6;
--color-tcb16-glow: oklch(0.22 0.03 280 / 0.9);
--color-tcb16-btn: #13121e;
--color-tcb16-btn: oklch(0.17 0.02 280);
--color-tcb16-c1: #3de6f2;
--color-tcb16-c1: oklch(0.85 0.15 195);
--color-tcb16-c2: #f0369b;
--color-tcb16-c2: oklch(0.65 0.26 345);
--color-tcb16-mut: #9cc9cf;
--color-tcb16-mut: oklch(0.8 0.05 200);
--font-tcb16-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tcb16-g1: tcb16-g1 0.32s steps(2, jump-none) infinite;
--animate-tcb16-g2: tcb16-g2 0.44s steps(2, jump-none) infinite;
@keyframes tcb16-g1 {
0% {
transform: translateX(-3px);
clip-path: inset(0 0 62% 0);
}
23% {
transform: translateX(2px);
clip-path: inset(38% 0 21% 0);
}
41% {
transform: translateX(-2px);
clip-path: inset(71% 0 0 0);
}
67% {
transform: translateX(3px);
clip-path: inset(12% 0 55% 0);
}
100% {
transform: translateX(-1px);
clip-path: inset(45% 0 32% 0);
}
}
@keyframes tcb16-g2 {
0% {
transform: translateX(3px);
clip-path: inset(58% 0 8% 0);
}
19% {
transform: translateX(-2px);
clip-path: inset(9% 0 70% 0);
}
47% {
transform: translateX(2px);
clip-path: inset(33% 0 40% 0);
}
73% {
transform: translateX(-3px);
clip-path: inset(80% 0 2% 0);
}
100% {
transform: translateX(1px);
clip-path: inset(21% 0 48% 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-16 {
width: 100%;
min-height: 100svh;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tcb16-bg: #0b0a14;
--color-tcb16-bg: oklch(0.13 0.015 280);
--color-tcb16-glow: #1c1a2ee6;
--color-tcb16-glow: oklch(0.22 0.03 280 / 0.9);
--color-tcb16-btn: #13121e;
--color-tcb16-btn: oklch(0.17 0.02 280);
--color-tcb16-c1: #3de6f2;
--color-tcb16-c1: oklch(0.85 0.15 195);
--color-tcb16-c2: #f0369b;
--color-tcb16-c2: oklch(0.65 0.26 345);
--color-tcb16-mut: #9cc9cf;
--color-tcb16-mut: oklch(0.8 0.05 200);
--font-tcb16-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tcb16-g1: tcb16-g1 0.32s steps(2, jump-none) infinite;
--animate-tcb16-g2: tcb16-g2 0.44s steps(2, jump-none) infinite;
@keyframes tcb16-g1 {
0% {
transform: translateX(-3px);
clip-path: inset(0 0 62% 0);
}
23% {
transform: translateX(2px);
clip-path: inset(38% 0 21% 0);
}
41% {
transform: translateX(-2px);
clip-path: inset(71% 0 0 0);
}
67% {
transform: translateX(3px);
clip-path: inset(12% 0 55% 0);
}
100% {
transform: translateX(-1px);
clip-path: inset(45% 0 32% 0);
}
}
@keyframes tcb16-g2 {
0% {
transform: translateX(3px);
clip-path: inset(58% 0 8% 0);
}
19% {
transform: translateX(-2px);
clip-path: inset(9% 0 70% 0);
}
47% {
transform: translateX(2px);
clip-path: inset(33% 0 40% 0);
}
73% {
transform: translateX(-3px);
clip-path: inset(80% 0 2% 0);
}
100% {
transform: translateX(1px);
clip-path: inset(21% 0 48% 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-16 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tcb16-bg: #0b0a14;
--color-tcb16-bg: oklch(0.13 0.015 280);
--color-tcb16-glow: #1c1a2ee6;
--color-tcb16-glow: oklch(0.22 0.03 280 / 0.9);
--color-tcb16-btn: #13121e;
--color-tcb16-btn: oklch(0.17 0.02 280);
--color-tcb16-c1: #3de6f2;
--color-tcb16-c1: oklch(0.85 0.15 195);
--color-tcb16-c2: #f0369b;
--color-tcb16-c2: oklch(0.65 0.26 345);
--color-tcb16-mut: #9cc9cf;
--color-tcb16-mut: oklch(0.8 0.05 200);
--font-tcb16-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tcb16-g1: tcb16-g1 0.32s steps(2, jump-none) infinite;
--animate-tcb16-g2: tcb16-g2 0.44s steps(2, jump-none) infinite;
@keyframes tcb16-g1 {
0% {
transform: translateX(-3px);
clip-path: inset(0 0 62% 0);
}
23% {
transform: translateX(2px);
clip-path: inset(38% 0 21% 0);
}
41% {
transform: translateX(-2px);
clip-path: inset(71% 0 0 0);
}
67% {
transform: translateX(3px);
clip-path: inset(12% 0 55% 0);
}
100% {
transform: translateX(-1px);
clip-path: inset(45% 0 32% 0);
}
}
@keyframes tcb16-g2 {
0% {
transform: translateX(3px);
clip-path: inset(58% 0 8% 0);
}
19% {
transform: translateX(-2px);
clip-path: inset(9% 0 70% 0);
}
47% {
transform: translateX(2px);
clip-path: inset(33% 0 40% 0);
}
73% {
transform: translateX(-3px);
clip-path: inset(80% 0 2% 0);
}
100% {
transform: translateX(1px);
clip-path: inset(21% 0 48% 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-16 {
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=JetBrains+Mono:wght@400;500;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=JetBrains+Mono:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The scoped
--barscustom property became an arbitrary property on the glyph, read bybg-(image:--tcb16-bars)on the span and both pseudo-elements. - Both glitch
@keyframesmoved into@themeas--animate-tcb16-g1/g2. - UI upgrade from the CSS original: focusing the button with the keyboard now triggers the glitch too, and the stage switched to JetBrains Mono so the caption reads as terminal output.
- UI upgrade: under reduced motion the ghosts are
hiddenrather than merely paused mid-tear.
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: Glitch Effect Tailwind Close Button
Source: https://codefronts.com/components/tailwind-css-close-buttons/glitch-cross/
RGB-split glitch on a cyber-terminal button: the × is painted from two gradient bars stored in one arbitrary custom property, and two tinted ghost copies — cyan and magenta via before:text-* and currentColor — jitter on desynced steps() tracks while clip-path: inset() tears them into scanlines. Keyframes live in @theme; the rest is markup.
## HTML
```html
<section class="tcb-16 w-full min-h-svh grid place-items-center bg-tcb16-bg bg-[radial-gradient(100%_110%_at_50%_0%,var(--color-tcb16-glow),transparent_60%)] px-6 py-12 font-tcb16-mono" aria-label="Glitch close button demo">
<div class="grid w-full justify-items-center gap-7">
<button type="button" aria-label="Close" class="group isolate grid size-18 cursor-pointer place-items-center rounded-xl border border-tcb16-c1/35 bg-tcb16-btn shadow-[0_0_0_1px_rgb(0_0_0/0.6),0_18px_40px_-20px_rgb(0_0_0/0.9)] transition-colors duration-200 hover:border-tcb16-c1 focus-visible:border-tcb16-c1 focus-visible:outline-[2.5px] focus-visible:outline-offset-3 focus-visible:outline-tcb16-c1 active:scale-93 motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-6.5 bg-(image:--tcb16-bars) text-white [--tcb16-bars:linear-gradient(45deg,transparent_calc(50%_-_1.25px),currentColor_calc(50%_-_1.25px)_calc(50%_+_1.25px),transparent_calc(50%_+_1.25px)),linear-gradient(-45deg,transparent_calc(50%_-_1.25px),currentColor_calc(50%_-_1.25px)_calc(50%_+_1.25px),transparent_calc(50%_+_1.25px))] before:absolute before:inset-0 before:bg-(image:--tcb16-bars) before:text-tcb16-c1 before:opacity-0 before:mix-blend-screen after:absolute after:inset-0 after:bg-(image:--tcb16-bars) after:text-tcb16-c2 after:opacity-0 after:mix-blend-screen group-hover:before:animate-tcb16-g1 group-hover:before:opacity-100 group-hover:after:animate-tcb16-g2 group-hover:after:opacity-100 group-focus-visible:before:animate-tcb16-g1 group-focus-visible:before:opacity-100 group-focus-visible:after:animate-tcb16-g2 group-focus-visible:after:opacity-100 motion-reduce:before:hidden motion-reduce:after:hidden"></span>
</button>
<p aria-hidden="true" class="rounded-md border border-tcb16-c1/25 px-3.5 py-2 text-[11.5px] text-tcb16-mut">bg-(image:--tcb16-bars) · before:mix-blend-screen · steps(2,jump-none)</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcb16-bg: #0b0a14;
--color-tcb16-bg: oklch(0.13 0.015 280);
--color-tcb16-glow: #1c1a2ee6;
--color-tcb16-glow: oklch(0.22 0.03 280 / 0.9);
--color-tcb16-btn: #13121e;
--color-tcb16-btn: oklch(0.17 0.02 280);
--color-tcb16-c1: #3de6f2;
--color-tcb16-c1: oklch(0.85 0.15 195);
--color-tcb16-c2: #f0369b;
--color-tcb16-c2: oklch(0.65 0.26 345);
--color-tcb16-mut: #9cc9cf;
--color-tcb16-mut: oklch(0.8 0.05 200);
--font-tcb16-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tcb16-g1: tcb16-g1 0.32s steps(2, jump-none) infinite;
--animate-tcb16-g2: tcb16-g2 0.44s steps(2, jump-none) infinite;
@keyframes tcb16-g1 {
0% {
transform: translateX(-3px);
clip-path: inset(0 0 62% 0);
}
23% {
transform: translateX(2px);
clip-path: inset(38% 0 21% 0);
}
41% {
transform: translateX(-2px);
clip-path: inset(71% 0 0 0);
}
67% {
transform: translateX(3px);
clip-path: inset(12% 0 55% 0);
}
100% {
transform: translateX(-1px);
clip-path: inset(45% 0 32% 0);
}
}
@keyframes tcb16-g2 {
0% {
transform: translateX(3px);
clip-path: inset(58% 0 8% 0);
}
19% {
transform: translateX(-2px);
clip-path: inset(9% 0 70% 0);
}
47% {
transform: translateX(2px);
clip-path: inset(33% 0 40% 0);
}
73% {
transform: translateX(-3px);
clip-path: inset(80% 0 2% 0);
}
100% {
transform: translateX(1px);
clip-path: inset(21% 0 48% 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-16 {
width: 100%;
min-height: 100svh;
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Glitch Effect Tailwind Close Button
Source: https://codefronts.com/components/tailwind-css-close-buttons/glitch-cross/
RGB-split glitch on a cyber-terminal button: the × is painted from two gradient bars stored in one arbitrary custom property, and two tinted ghost copies — cyan and magenta via before:text-* and currentColor — jitter on desynced steps() tracks while clip-path: inset() tears them into scanlines. Keyframes live in @theme; the rest is markup.
## HTML
```html
<section class="tcb-16 w-full min-h-svh grid place-items-center bg-tcb16-bg bg-[radial-gradient(100%_110%_at_50%_0%,var(--color-tcb16-glow),transparent_60%)] px-6 py-12 font-tcb16-mono" aria-label="Glitch close button demo">
<div class="grid w-full justify-items-center gap-7">
<button type="button" aria-label="Close" class="group isolate grid size-18 cursor-pointer place-items-center rounded-xl border border-tcb16-c1/35 bg-tcb16-btn shadow-[0_0_0_1px_rgb(0_0_0/0.6),0_18px_40px_-20px_rgb(0_0_0/0.9)] transition-colors duration-200 hover:border-tcb16-c1 focus-visible:border-tcb16-c1 focus-visible:outline-[2.5px] focus-visible:outline-offset-3 focus-visible:outline-tcb16-c1 active:scale-93 motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-6.5 bg-(image:--tcb16-bars) text-white [--tcb16-bars:linear-gradient(45deg,transparent_calc(50%_-_1.25px),currentColor_calc(50%_-_1.25px)_calc(50%_+_1.25px),transparent_calc(50%_+_1.25px)),linear-gradient(-45deg,transparent_calc(50%_-_1.25px),currentColor_calc(50%_-_1.25px)_calc(50%_+_1.25px),transparent_calc(50%_+_1.25px))] before:absolute before:inset-0 before:bg-(image:--tcb16-bars) before:text-tcb16-c1 before:opacity-0 before:mix-blend-screen after:absolute after:inset-0 after:bg-(image:--tcb16-bars) after:text-tcb16-c2 after:opacity-0 after:mix-blend-screen group-hover:before:animate-tcb16-g1 group-hover:before:opacity-100 group-hover:after:animate-tcb16-g2 group-hover:after:opacity-100 group-focus-visible:before:animate-tcb16-g1 group-focus-visible:before:opacity-100 group-focus-visible:after:animate-tcb16-g2 group-focus-visible:after:opacity-100 motion-reduce:before:hidden motion-reduce:after:hidden"></span>
</button>
<p aria-hidden="true" class="rounded-md border border-tcb16-c1/25 px-3.5 py-2 text-[11.5px] text-tcb16-mut">bg-(image:--tcb16-bars) · before:mix-blend-screen · steps(2,jump-none)</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcb16-bg: #0b0a14;
--color-tcb16-bg: oklch(0.13 0.015 280);
--color-tcb16-glow: #1c1a2ee6;
--color-tcb16-glow: oklch(0.22 0.03 280 / 0.9);
--color-tcb16-btn: #13121e;
--color-tcb16-btn: oklch(0.17 0.02 280);
--color-tcb16-c1: #3de6f2;
--color-tcb16-c1: oklch(0.85 0.15 195);
--color-tcb16-c2: #f0369b;
--color-tcb16-c2: oklch(0.65 0.26 345);
--color-tcb16-mut: #9cc9cf;
--color-tcb16-mut: oklch(0.8 0.05 200);
--font-tcb16-mono: 'JetBrains Mono', ui-monospace, monospace;
--animate-tcb16-g1: tcb16-g1 0.32s steps(2, jump-none) infinite;
--animate-tcb16-g2: tcb16-g2 0.44s steps(2, jump-none) infinite;
@keyframes tcb16-g1 {
0% {
transform: translateX(-3px);
clip-path: inset(0 0 62% 0);
}
23% {
transform: translateX(2px);
clip-path: inset(38% 0 21% 0);
}
41% {
transform: translateX(-2px);
clip-path: inset(71% 0 0 0);
}
67% {
transform: translateX(3px);
clip-path: inset(12% 0 55% 0);
}
100% {
transform: translateX(-1px);
clip-path: inset(45% 0 32% 0);
}
}
@keyframes tcb16-g2 {
0% {
transform: translateX(3px);
clip-path: inset(58% 0 8% 0);
}
19% {
transform: translateX(-2px);
clip-path: inset(9% 0 70% 0);
}
47% {
transform: translateX(2px);
clip-path: inset(33% 0 40% 0);
}
73% {
transform: translateX(-3px);
clip-path: inset(80% 0 2% 0);
}
100% {
transform: translateX(1px);
clip-path: inset(21% 0 48% 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-16 {
width: 100%;
min-height: 100svh;
}
```How this works
One definition, three inks. A custom property holding currentColor stays unresolved until it is used, so the same gradient paints white on the span and cyan/magenta on its pseudo-elements:
[--tcb16-bars:linear-gradient(45deg,transparent_calc(50%_-_1.25px),currentColor_…)]
bg-(image:--tcb16-bars) text-white
before:bg-(image:--tcb16-bars) before:text-tcb16-c1Desync is the trick. The ghosts run 0.32s and 0.44s loops; their least common multiple is long enough that the pattern never visibly repeats.
Make it yours
- Calmer glitch: slow the loops to
0.6s/0.8s. - Monochrome: set both ghost colours to
white/60for a VHS look. - Thicker ×: change
1.25pxto1.75pxin the bar gradient.
Gotchas — read before shipping
- Arbitrary values need
_for spaces; insidecalc()write50%_-_1.25pxso the operator keeps its spaces. - Flashing more than three times per second can trigger seizures — the reduced-motion variant removes the ghosts entirely.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
The floor is Tailwind v4; steps(…, jump-none) is universal on it.