16 / 51
All 51 in 51 Tailwind CSS Buttons
- 01Tailwind Submit Button (Form Primary)
- 02Tailwind Primary and Secondary Button Pair
- 03Tailwind Ghost / Outline Button
- 04Tailwind Icon-Only Button with Accessible Tooltip
- 05Tailwind Gradient CTA Button
- 06Tailwind Loading Spinner Button (State Transition)
- 07Tailwind Disabled Button State (Full State Reference)
- 08Tailwind Button Group Component (Segmented Radio-Like)
- 09Tailwind Add to Cart Button
- 10Tailwind Download Progress Button
- 11Tailwind Like Heart Button
- 12Tailwind Confirm Delete Button
- 13Tailwind Play Pause Button
- 14Tailwind Subscribe Confetti Button
- 15Tailwind Magnetic Mercury Ripple Button
- 16Tailwind Brutalist Glitch Button
- 17Tailwind Neon Plasma Burst Button
- 18Tailwind Clay Press Button
- 19Tailwind Editorial Ink Spread Button
- 20Tailwind Particle Shard Burst Button
- 21Tailwind Brushed Brass Plate Button
- 22Tailwind Washi Paper Crane Button
- 23Tailwind Frosted Ice Cube Button
- 24Tailwind Vintage Typewriter Key Button
- 25Tailwind Lacquered Walnut Wood Button
- 26Tailwind Pharma Gel Capsule Button
- 27Tailwind Aged Copper Coin Button
- 28Tailwind Origami Fortune Cookie Button
- 29Tailwind Holographic Foil Sticker Button
- 30Tailwind Brutalist Concrete Slab Button
- 31Tailwind Mercury Bead Morph Button
- 32Tailwind Soft Pill Modern SaaS Button
- 33Tailwind Masking Tape Label Button
- 34Tailwind Obsidian Glass Shard Button
- 35Tailwind Wine Velvet Ribbon Button
- 36Tailwind LED Segment Display Button
- 37Tailwind Cassette Mixtape Label Button
- 38Tailwind Chalkboard Menu Button
- 39Tailwind Vinyl Record Sleeve Ripple Button
- 40Tailwind Polaroid Snapshot Button
- 41Tailwind Airline Boarding Pass Button
- 42Tailwind Aged Brass Subway Token Button
- 43Tailwind Vintage Rx Pharmacy Label Button
- 44Tailwind Bakelite Radio Dial Toggle Button
- 45Tailwind Regal Postage Stamp Button
- 46Tailwind Victorian Cigar Band Foil Button
- 47Tailwind Vintage Calculator Key Button
- 48Tailwind Toolbox Latch Spotlight Button
- 49Tailwind Receipt Paper Total Count-Up Button
- 50Tailwind IBM Punch Card Magnetic Follow Button
- 51Tailwind Brass Periscope Lens Button
Tailwind Brutalist Glitch Button
Neo-brutalist slab with a hard offset shadow that snaps flat when pressed, and a hover glitch built from two duplicate labels sliced by clip-path and pushed apart in cyan and magenta. Steps-based timing keeps it digital — no easing, no blur, no JavaScript.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tb-16 w-full min-h-svh grid place-items-center font-tb16-mono p-[clamp(1.5rem,6vw,4rem)]"> <button type="button" data-label="Enter the archive" class="relative cursor-pointer grid place-items-center min-h-14 px-8 border-2 border-tb16-ink rounded-none text-[0.9375rem] font-bold uppercase tracking-[0.1em] text-tb16-ink bg-tb16-slab shadow-[6px_6px_0_0_var(--color-tb16-ink)] transition-[translate,box-shadow,background-color] duration-[120ms] ease-[steps(2,end)] before:content-[attr(data-label)] before:absolute before:inset-0 before:grid before:place-items-center before:px-8 before:whitespace-nowrap before:pointer-events-none before:opacity-0 before:text-tb16-ghost-1 before:[clip-path:inset(0_0_55%_0)] after:content-[attr(data-label)] after:absolute after:inset-0 after:grid after:place-items-center after:px-8 after:whitespace-nowrap after:pointer-events-none after:opacity-0 after:text-tb16-ghost-2 after:[clip-path:inset(55%_0_0_0)] hover:bg-tb16-accent hover:before:animate-tb16-a hover:after:animate-tb16-b focus-visible:bg-tb16-accent focus-visible:before:animate-tb16-a focus-visible:after:animate-tb16-b focus-visible:outline-[3px] focus-visible:outline-dashed focus-visible:outline-tb16-ink focus-visible:outline-offset-[5px] active:translate-x-1.5 active:translate-y-1.5 active:shadow-none disabled:cursor-not-allowed disabled:text-tb16-ink-off disabled:shadow-[6px_6px_0_0_var(--color-tb16-ink-off)] motion-reduce:transition-none motion-reduce:before:hidden motion-reduce:after:hidden">Enter the archive</button> </div>
<div class="tb-16 w-full min-h-svh grid place-items-center font-tb16-mono p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" data-label="Enter the archive" class="relative cursor-pointer grid place-items-center min-h-14 px-8 border-2 border-tb16-ink rounded-none text-[0.9375rem] font-bold uppercase tracking-[0.1em] text-tb16-ink bg-tb16-slab shadow-[6px_6px_0_0_var(--color-tb16-ink)] transition-[translate,box-shadow,background-color] duration-[120ms] ease-[steps(2,end)] before:content-[attr(data-label)] before:absolute before:inset-0 before:grid before:place-items-center before:px-8 before:whitespace-nowrap before:pointer-events-none before:opacity-0 before:text-tb16-ghost-1 before:[clip-path:inset(0_0_55%_0)] after:content-[attr(data-label)] after:absolute after:inset-0 after:grid after:place-items-center after:px-8 after:whitespace-nowrap after:pointer-events-none after:opacity-0 after:text-tb16-ghost-2 after:[clip-path:inset(55%_0_0_0)] hover:bg-tb16-accent hover:before:animate-tb16-a hover:after:animate-tb16-b focus-visible:bg-tb16-accent focus-visible:before:animate-tb16-a focus-visible:after:animate-tb16-b focus-visible:outline-[3px] focus-visible:outline-dashed focus-visible:outline-tb16-ink focus-visible:outline-offset-[5px] active:translate-x-1.5 active:translate-y-1.5 active:shadow-none disabled:cursor-not-allowed disabled:text-tb16-ink-off disabled:shadow-[6px_6px_0_0_var(--color-tb16-ink-off)] motion-reduce:transition-none motion-reduce:before:hidden motion-reduce:after:hidden">Enter the archive</button>
</div>@import "tailwindcss";
@theme {
--color-tb16-bg: #e8e6df;
--color-tb16-bg: oklch(0.92 0.008 95);
--color-tb16-slab: #f7f5ef;
--color-tb16-slab: oklch(0.97 0.008 95);
--color-tb16-ink: #0a0a0a;
--color-tb16-ink: oklch(0.15 0 0);
--color-tb16-ink-off: #7d7d78;
--color-tb16-ink-off: oklch(0.58 0.004 95);
--color-tb16-accent: #facc15;
--color-tb16-accent: oklch(0.87 0.17 92);
--color-tb16-ghost-1: #0e9aab;
--color-tb16-ghost-1: oklch(0.63 0.11 205);
--color-tb16-ghost-2: #d1214a;
--color-tb16-ghost-2: oklch(0.52 0.2 12);
--color-tb16-grid: #0a0a0a0d;
--color-tb16-grid: oklch(0.15 0 0 / 0.05);
--font-tb16-mono: "Space Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
}
@keyframes tb-16-a {
0%,
100% {
translate: 0 0;
opacity: 0;
}
20% {
translate: -3px -2px;
opacity: 1;
}
45% {
translate: 4px 1px;
opacity: 1;
clip-path: inset(0 0 68% 0);
}
70% {
translate: -2px 2px;
opacity: 1;
clip-path: inset(0 0 42% 0);
}
}
@keyframes tb-16-b {
0%,
100% {
translate: 0 0;
opacity: 0;
}
25% {
translate: 3px 2px;
opacity: 1;
}
50% {
translate: -4px -1px;
opacity: 1;
clip-path: inset(48% 0 0 0);
}
75% {
translate: 2px -2px;
opacity: 1;
clip-path: inset(62% 0 0 0);
}
}
@theme {
--animate-tb16-a: tb-16-a 0.48s steps(2, end) infinite;
--animate-tb16-b: tb-16-b 0.56s steps(2, end) infinite;
}
/* The graph-paper stage: two repeating gradients over the base. Scene, not component. */
.tb-16 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: repeating-linear-gradient(90deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), repeating-linear-gradient(0deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), var(--color-tb16-bg);
}
@media (prefers-color-scheme: dark) {
.tb-16:not([data-theme="light"]) {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-ink-off: oklch(0.55 0.004 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
--color-tb16-ghost-1: oklch(0.82 0.13 200);
--color-tb16-ghost-2: oklch(0.7 0.2 12);
}
}
.tb-16[data-theme="dark"] {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
}
@media (forced-colors: active) {
.tb-16 button {
background: ButtonFace;
color: ButtonText;
border-color: ButtonText;
box-shadow: 6px 6px 0 0 ButtonText;
}
.tb-16 button::before,
.tb-16 button::after {
display: none;
}
}@import "tailwindcss";
@theme {
--color-tb16-bg: #e8e6df;
--color-tb16-bg: oklch(0.92 0.008 95);
--color-tb16-slab: #f7f5ef;
--color-tb16-slab: oklch(0.97 0.008 95);
--color-tb16-ink: #0a0a0a;
--color-tb16-ink: oklch(0.15 0 0);
--color-tb16-ink-off: #7d7d78;
--color-tb16-ink-off: oklch(0.58 0.004 95);
--color-tb16-accent: #facc15;
--color-tb16-accent: oklch(0.87 0.17 92);
--color-tb16-ghost-1: #0e9aab;
--color-tb16-ghost-1: oklch(0.63 0.11 205);
--color-tb16-ghost-2: #d1214a;
--color-tb16-ghost-2: oklch(0.52 0.2 12);
--color-tb16-grid: #0a0a0a0d;
--color-tb16-grid: oklch(0.15 0 0 / 0.05);
--font-tb16-mono: "Space Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
}
@keyframes tb-16-a {
0%,
100% {
translate: 0 0;
opacity: 0;
}
20% {
translate: -3px -2px;
opacity: 1;
}
45% {
translate: 4px 1px;
opacity: 1;
clip-path: inset(0 0 68% 0);
}
70% {
translate: -2px 2px;
opacity: 1;
clip-path: inset(0 0 42% 0);
}
}
@keyframes tb-16-b {
0%,
100% {
translate: 0 0;
opacity: 0;
}
25% {
translate: 3px 2px;
opacity: 1;
}
50% {
translate: -4px -1px;
opacity: 1;
clip-path: inset(48% 0 0 0);
}
75% {
translate: 2px -2px;
opacity: 1;
clip-path: inset(62% 0 0 0);
}
}
@theme {
--animate-tb16-a: tb-16-a 0.48s steps(2, end) infinite;
--animate-tb16-b: tb-16-b 0.56s steps(2, end) infinite;
}
/* The graph-paper stage: two repeating gradients over the base. Scene, not component. */
.tb-16 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: repeating-linear-gradient(90deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), repeating-linear-gradient(0deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), var(--color-tb16-bg);
}
@media (prefers-color-scheme: dark) {
.tb-16:not([data-theme="light"]) {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-ink-off: oklch(0.55 0.004 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
--color-tb16-ghost-1: oklch(0.82 0.13 200);
--color-tb16-ghost-2: oklch(0.7 0.2 12);
}
}
.tb-16[data-theme="dark"] {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
}
@media (forced-colors: active) {
.tb-16 button {
background: ButtonFace;
color: ButtonText;
border-color: ButtonText;
box-shadow: 6px 6px 0 0 ButtonText;
}
.tb-16 button::before,
.tb-16 button::after {
display: none;
}
}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-tb16-bg: #e8e6df;
--color-tb16-bg: oklch(0.92 0.008 95);
--color-tb16-slab: #f7f5ef;
--color-tb16-slab: oklch(0.97 0.008 95);
--color-tb16-ink: #0a0a0a;
--color-tb16-ink: oklch(0.15 0 0);
--color-tb16-ink-off: #7d7d78;
--color-tb16-ink-off: oklch(0.58 0.004 95);
--color-tb16-accent: #facc15;
--color-tb16-accent: oklch(0.87 0.17 92);
--color-tb16-ghost-1: #0e9aab;
--color-tb16-ghost-1: oklch(0.63 0.11 205);
--color-tb16-ghost-2: #d1214a;
--color-tb16-ghost-2: oklch(0.52 0.2 12);
--color-tb16-grid: #0a0a0a0d;
--color-tb16-grid: oklch(0.15 0 0 / 0.05);
--font-tb16-mono: "Space Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
}
@keyframes tb-16-a {
0%,
100% {
translate: 0 0;
opacity: 0;
}
20% {
translate: -3px -2px;
opacity: 1;
}
45% {
translate: 4px 1px;
opacity: 1;
clip-path: inset(0 0 68% 0);
}
70% {
translate: -2px 2px;
opacity: 1;
clip-path: inset(0 0 42% 0);
}
}
@keyframes tb-16-b {
0%,
100% {
translate: 0 0;
opacity: 0;
}
25% {
translate: 3px 2px;
opacity: 1;
}
50% {
translate: -4px -1px;
opacity: 1;
clip-path: inset(48% 0 0 0);
}
75% {
translate: 2px -2px;
opacity: 1;
clip-path: inset(62% 0 0 0);
}
}
@theme {
--animate-tb16-a: tb-16-a 0.48s steps(2, end) infinite;
--animate-tb16-b: tb-16-b 0.56s steps(2, end) infinite;
}
/* The graph-paper stage: two repeating gradients over the base. Scene, not component. */
.tb-16 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: repeating-linear-gradient(90deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), repeating-linear-gradient(0deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), var(--color-tb16-bg);
}
@media (prefers-color-scheme: dark) {
.tb-16:not([data-theme="light"]) {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-ink-off: oklch(0.55 0.004 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
--color-tb16-ghost-1: oklch(0.82 0.13 200);
--color-tb16-ghost-2: oklch(0.7 0.2 12);
}
}
.tb-16[data-theme="dark"] {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
}
@media (forced-colors: active) {
.tb-16 button {
background: ButtonFace;
color: ButtonText;
border-color: ButtonText;
box-shadow: 6px 6px 0 0 ButtonText;
}
.tb-16 button::before,
.tb-16 button::after {
display: none;
}
}@import "tailwindcss";
@theme {
--color-tb16-bg: #e8e6df;
--color-tb16-bg: oklch(0.92 0.008 95);
--color-tb16-slab: #f7f5ef;
--color-tb16-slab: oklch(0.97 0.008 95);
--color-tb16-ink: #0a0a0a;
--color-tb16-ink: oklch(0.15 0 0);
--color-tb16-ink-off: #7d7d78;
--color-tb16-ink-off: oklch(0.58 0.004 95);
--color-tb16-accent: #facc15;
--color-tb16-accent: oklch(0.87 0.17 92);
--color-tb16-ghost-1: #0e9aab;
--color-tb16-ghost-1: oklch(0.63 0.11 205);
--color-tb16-ghost-2: #d1214a;
--color-tb16-ghost-2: oklch(0.52 0.2 12);
--color-tb16-grid: #0a0a0a0d;
--color-tb16-grid: oklch(0.15 0 0 / 0.05);
--font-tb16-mono: "Space Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
}
@keyframes tb-16-a {
0%,
100% {
translate: 0 0;
opacity: 0;
}
20% {
translate: -3px -2px;
opacity: 1;
}
45% {
translate: 4px 1px;
opacity: 1;
clip-path: inset(0 0 68% 0);
}
70% {
translate: -2px 2px;
opacity: 1;
clip-path: inset(0 0 42% 0);
}
}
@keyframes tb-16-b {
0%,
100% {
translate: 0 0;
opacity: 0;
}
25% {
translate: 3px 2px;
opacity: 1;
}
50% {
translate: -4px -1px;
opacity: 1;
clip-path: inset(48% 0 0 0);
}
75% {
translate: 2px -2px;
opacity: 1;
clip-path: inset(62% 0 0 0);
}
}
@theme {
--animate-tb16-a: tb-16-a 0.48s steps(2, end) infinite;
--animate-tb16-b: tb-16-b 0.56s steps(2, end) infinite;
}
/* The graph-paper stage: two repeating gradients over the base. Scene, not component. */
.tb-16 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: repeating-linear-gradient(90deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), repeating-linear-gradient(0deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), var(--color-tb16-bg);
}
@media (prefers-color-scheme: dark) {
.tb-16:not([data-theme="light"]) {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-ink-off: oklch(0.55 0.004 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
--color-tb16-ghost-1: oklch(0.82 0.13 200);
--color-tb16-ghost-2: oklch(0.7 0.2 12);
}
}
.tb-16[data-theme="dark"] {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
}
@media (forced-colors: active) {
.tb-16 button {
background: ButtonFace;
color: ButtonText;
border-color: ButtonText;
box-shadow: 6px 6px 0 0 ButtonText;
}
.tb-16 button::before,
.tb-16 button::after {
display: none;
}
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Utilities: the slab, the hard shadow, the flush press, both ghost layers with their clip slices and colours, and the hover/focus animation triggers. CSS: the two glitch keyframes and the graph-paper stage.
before:content-[attr(data-label)]is the port's neatest trick —attr()survives inside an arbitrary content utility, so the duplicate-label technique needs no extra markup and no repeated string.- The
@themeblock is split in two so the--animate-*entries can follow their@keyframes. Order does not matter to the compiler, but reading the keyframe before the token that names it does matter to a human. - A11y fix from the CSS original: the ghost colours were
#22d3eeand#f43f5e— fine as decoration, but they flash directly over the label during the glitch and both sat near 2:1 against the yellow hover fill, making the word briefly unreadable. The port darkens them to ≈4.6:1 and ≈5.1:1, so every frame of the glitch is still legible.
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 Brutalist Glitch Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-brutalist-glitch-button/
Neo-brutalist slab with a hard offset shadow that snaps flat when pressed, and a hover glitch built from two duplicate labels sliced by clip-path and pushed apart in cyan and magenta. Steps-based timing keeps it digital — no easing, no blur, no JavaScript.
## HTML
```html
<div class="tb-16 w-full min-h-svh grid place-items-center font-tb16-mono p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" data-label="Enter the archive" class="relative cursor-pointer grid place-items-center min-h-14 px-8 border-2 border-tb16-ink rounded-none text-[0.9375rem] font-bold uppercase tracking-[0.1em] text-tb16-ink bg-tb16-slab shadow-[6px_6px_0_0_var(--color-tb16-ink)] transition-[translate,box-shadow,background-color] duration-[120ms] ease-[steps(2,end)] before:content-[attr(data-label)] before:absolute before:inset-0 before:grid before:place-items-center before:px-8 before:whitespace-nowrap before:pointer-events-none before:opacity-0 before:text-tb16-ghost-1 before:[clip-path:inset(0_0_55%_0)] after:content-[attr(data-label)] after:absolute after:inset-0 after:grid after:place-items-center after:px-8 after:whitespace-nowrap after:pointer-events-none after:opacity-0 after:text-tb16-ghost-2 after:[clip-path:inset(55%_0_0_0)] hover:bg-tb16-accent hover:before:animate-tb16-a hover:after:animate-tb16-b focus-visible:bg-tb16-accent focus-visible:before:animate-tb16-a focus-visible:after:animate-tb16-b focus-visible:outline-[3px] focus-visible:outline-dashed focus-visible:outline-tb16-ink focus-visible:outline-offset-[5px] active:translate-x-1.5 active:translate-y-1.5 active:shadow-none disabled:cursor-not-allowed disabled:text-tb16-ink-off disabled:shadow-[6px_6px_0_0_var(--color-tb16-ink-off)] motion-reduce:transition-none motion-reduce:before:hidden motion-reduce:after:hidden">Enter the archive</button>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb16-bg: #e8e6df;
--color-tb16-bg: oklch(0.92 0.008 95);
--color-tb16-slab: #f7f5ef;
--color-tb16-slab: oklch(0.97 0.008 95);
--color-tb16-ink: #0a0a0a;
--color-tb16-ink: oklch(0.15 0 0);
--color-tb16-ink-off: #7d7d78;
--color-tb16-ink-off: oklch(0.58 0.004 95);
--color-tb16-accent: #facc15;
--color-tb16-accent: oklch(0.87 0.17 92);
--color-tb16-ghost-1: #0e9aab;
--color-tb16-ghost-1: oklch(0.63 0.11 205);
--color-tb16-ghost-2: #d1214a;
--color-tb16-ghost-2: oklch(0.52 0.2 12);
--color-tb16-grid: #0a0a0a0d;
--color-tb16-grid: oklch(0.15 0 0 / 0.05);
--font-tb16-mono: "Space Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
}
@keyframes tb-16-a {
0%,
100% {
translate: 0 0;
opacity: 0;
}
20% {
translate: -3px -2px;
opacity: 1;
}
45% {
translate: 4px 1px;
opacity: 1;
clip-path: inset(0 0 68% 0);
}
70% {
translate: -2px 2px;
opacity: 1;
clip-path: inset(0 0 42% 0);
}
}
@keyframes tb-16-b {
0%,
100% {
translate: 0 0;
opacity: 0;
}
25% {
translate: 3px 2px;
opacity: 1;
}
50% {
translate: -4px -1px;
opacity: 1;
clip-path: inset(48% 0 0 0);
}
75% {
translate: 2px -2px;
opacity: 1;
clip-path: inset(62% 0 0 0);
}
}
@theme {
--animate-tb16-a: tb-16-a 0.48s steps(2, end) infinite;
--animate-tb16-b: tb-16-b 0.56s steps(2, end) infinite;
}
/* The graph-paper stage: two repeating gradients over the base. Scene, not component. */
.tb-16 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: repeating-linear-gradient(90deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), repeating-linear-gradient(0deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), var(--color-tb16-bg);
}
@media (prefers-color-scheme: dark) {
.tb-16:not([data-theme="light"]) {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-ink-off: oklch(0.55 0.004 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
--color-tb16-ghost-1: oklch(0.82 0.13 200);
--color-tb16-ghost-2: oklch(0.7 0.2 12);
}
}
.tb-16[data-theme="dark"] {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
}
@media (forced-colors: active) {
.tb-16 button {
background: ButtonFace;
color: ButtonText;
border-color: ButtonText;
box-shadow: 6px 6px 0 0 ButtonText;
}
.tb-16 button::before,
.tb-16 button::after {
display: none;
}
}
```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 Brutalist Glitch Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-brutalist-glitch-button/
Neo-brutalist slab with a hard offset shadow that snaps flat when pressed, and a hover glitch built from two duplicate labels sliced by clip-path and pushed apart in cyan and magenta. Steps-based timing keeps it digital — no easing, no blur, no JavaScript.
## HTML
```html
<div class="tb-16 w-full min-h-svh grid place-items-center font-tb16-mono p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" data-label="Enter the archive" class="relative cursor-pointer grid place-items-center min-h-14 px-8 border-2 border-tb16-ink rounded-none text-[0.9375rem] font-bold uppercase tracking-[0.1em] text-tb16-ink bg-tb16-slab shadow-[6px_6px_0_0_var(--color-tb16-ink)] transition-[translate,box-shadow,background-color] duration-[120ms] ease-[steps(2,end)] before:content-[attr(data-label)] before:absolute before:inset-0 before:grid before:place-items-center before:px-8 before:whitespace-nowrap before:pointer-events-none before:opacity-0 before:text-tb16-ghost-1 before:[clip-path:inset(0_0_55%_0)] after:content-[attr(data-label)] after:absolute after:inset-0 after:grid after:place-items-center after:px-8 after:whitespace-nowrap after:pointer-events-none after:opacity-0 after:text-tb16-ghost-2 after:[clip-path:inset(55%_0_0_0)] hover:bg-tb16-accent hover:before:animate-tb16-a hover:after:animate-tb16-b focus-visible:bg-tb16-accent focus-visible:before:animate-tb16-a focus-visible:after:animate-tb16-b focus-visible:outline-[3px] focus-visible:outline-dashed focus-visible:outline-tb16-ink focus-visible:outline-offset-[5px] active:translate-x-1.5 active:translate-y-1.5 active:shadow-none disabled:cursor-not-allowed disabled:text-tb16-ink-off disabled:shadow-[6px_6px_0_0_var(--color-tb16-ink-off)] motion-reduce:transition-none motion-reduce:before:hidden motion-reduce:after:hidden">Enter the archive</button>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb16-bg: #e8e6df;
--color-tb16-bg: oklch(0.92 0.008 95);
--color-tb16-slab: #f7f5ef;
--color-tb16-slab: oklch(0.97 0.008 95);
--color-tb16-ink: #0a0a0a;
--color-tb16-ink: oklch(0.15 0 0);
--color-tb16-ink-off: #7d7d78;
--color-tb16-ink-off: oklch(0.58 0.004 95);
--color-tb16-accent: #facc15;
--color-tb16-accent: oklch(0.87 0.17 92);
--color-tb16-ghost-1: #0e9aab;
--color-tb16-ghost-1: oklch(0.63 0.11 205);
--color-tb16-ghost-2: #d1214a;
--color-tb16-ghost-2: oklch(0.52 0.2 12);
--color-tb16-grid: #0a0a0a0d;
--color-tb16-grid: oklch(0.15 0 0 / 0.05);
--font-tb16-mono: "Space Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
}
@keyframes tb-16-a {
0%,
100% {
translate: 0 0;
opacity: 0;
}
20% {
translate: -3px -2px;
opacity: 1;
}
45% {
translate: 4px 1px;
opacity: 1;
clip-path: inset(0 0 68% 0);
}
70% {
translate: -2px 2px;
opacity: 1;
clip-path: inset(0 0 42% 0);
}
}
@keyframes tb-16-b {
0%,
100% {
translate: 0 0;
opacity: 0;
}
25% {
translate: 3px 2px;
opacity: 1;
}
50% {
translate: -4px -1px;
opacity: 1;
clip-path: inset(48% 0 0 0);
}
75% {
translate: 2px -2px;
opacity: 1;
clip-path: inset(62% 0 0 0);
}
}
@theme {
--animate-tb16-a: tb-16-a 0.48s steps(2, end) infinite;
--animate-tb16-b: tb-16-b 0.56s steps(2, end) infinite;
}
/* The graph-paper stage: two repeating gradients over the base. Scene, not component. */
.tb-16 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: repeating-linear-gradient(90deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), repeating-linear-gradient(0deg, var(--color-tb16-grid) 0 1px, transparent 1px 3rem), var(--color-tb16-bg);
}
@media (prefers-color-scheme: dark) {
.tb-16:not([data-theme="light"]) {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-ink-off: oklch(0.55 0.004 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
--color-tb16-ghost-1: oklch(0.82 0.13 200);
--color-tb16-ghost-2: oklch(0.7 0.2 12);
}
}
.tb-16[data-theme="dark"] {
--color-tb16-bg: oklch(0.16 0 0);
--color-tb16-slab: oklch(0.22 0 0);
--color-tb16-ink: oklch(0.96 0.006 95);
--color-tb16-grid: oklch(0.96 0.006 95 / 0.06);
}
@media (forced-colors: active) {
.tb-16 button {
background: ButtonFace;
color: ButtonText;
border-color: ButtonText;
box-shadow: 6px 6px 0 0 ButtonText;
}
.tb-16 button::before,
.tb-16 button::after {
display: none;
}
}
```How this works
The glitch layers are pseudo-elements reading a data attribute:
<button data-label="Enter the archive"
class="before:content-[attr(data-label)] before:absolute before:inset-0
before:text-tb16-ghost-1 before:[clip-path:inset(0_0_55%_0)]
hover:before:animate-tb16-a focus-visible:before:animate-tb16-a">The content is generated, so it never enters the accessibility tree — the real label is the button's own text, and the coloured ghosts are decoration by construction.
The press is the brutalist signature. --offset drives both the shadow distance and the press travel; they must match, or the flush-press illusion breaks. As utilities that constraint is at least visible on one line.
Both animations fire on focus-visible: as well as hover, matching the CSS original's :is(:hover,:focus-visible) — and the whole effect is hidden under motion-reduce:, because a stuttering label is a genuine photosensitivity risk.
Make it yours
shadow-[6px_6px_...]andactive:translate-*-1.5are the same 6px. Change both together.- Brand pair instead of cyan/magenta: edit
--color-tb16-ghost-1/2. ease-[steps(1,end)]for a harsher two-frame stutter;steps(4)for a busier one.- Delete the glitch entirely — the slab, border and shadow are a complete brutalist button on their own.
- Uppercase monospace is doing real work here; keep
tracking-[0.1em]or it collapses.
Gotchas — read before shipping
- Glitch animation is a photosensitivity risk. Hover-only, short, and fully disabled under
motion-reduce:— never on loop by default. content-[attr(data-label)]is invisible to assistive tech, which is correct: the pseudo-layers are decoration.- Brutalist palettes are high-contrast by accident, not design. Verify the label against the hover fill — this one hits 4.5:1 on yellow.
clip-pathon a pseudo-element clips anything drawn on it, including an outline. Keep the ring on the button.- Zero-blur shadows expose subpixel rounding. Whole-pixel offsets only.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
clip-path: inset(), steps() timing, attr() in content and individual transform properties are all baseline below Tailwind v4's floor.