07 / 20
All 20 in 20 Tailwind CSS Grain Texture
- 01Tailwind SVG feTurbulence Noise Parameter Breakdown
- 02Tailwind baseFrequency and numOctaves Grain Size Control
- 03Tailwind Data URI SVG Noise Texture Without an Image Request
- 04Tailwind Grain Opacity and mix-blend-mode Composite Matrix
- 05Tailwind Tinted SVG Noise with feColorMatrix
- 06Tailwind Grain Paint Cost and Byte Size Measured Live
- 07Tailwind Anti-Banding Grainy CSS Gradient Hero
- 08Tailwind Vignette Photo Overlay with Film Grain
- 09Tailwind Tactile Card Surface with Subtle Noise
- 10Tailwind Full-Bleed Hero Section Noise Background
- 11Tailwind Noise Layer Order Over backdrop-filter Glass
- 12Tailwind Textured Button and Input Surfaces
- 13Tailwind Textured Section Divider with SVG Grain
- 14Tailwind Static Halftone Dot Print Effect
- 15Tailwind Dithered Bayer Matrix Shading Effect
- 16Tailwind Paper Stock and Letterpress Press Effect
- 17Tailwind Risograph Print Misregistration Texture
- 18Tailwind Woven Fabric and Canvas Texture Surface
- 19Tailwind Dark Mode Noise Without Muddy Shadows
- 20Tailwind Reduced Motion Static Grain Fallback
Tailwind Anti-Banding Grainy CSS Gradient Hero
Banding is not a bug in your gradient, it is the 8-bit display pipeline showing through — and the fix is the one film and print have used for a century: add noise. A full-bleed dusk hero interpolated in oklab, stopped through the dark third, dithered at 3.5%, plus a side-by-side of the same ramp treated and untreated.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="gtw-07 relative block w-full min-h-svh bg-gtw07-bg text-gtw07-ink" aria-labelledby="gtw-07-title">
<div class="absolute top-[clamp(1rem,3vw,1.75rem)] right-[clamp(0.85rem,3vw,1.75rem)] z-40">
<input type="checkbox" id="gtw-07-theme" class="gtw-07-theme peer sr-only">
<label for="gtw-07-theme" class="grid place-items-center w-11 h-11 rounded-full border border-white/30 bg-[oklch(0.15_0.03_280/0.4)] backdrop-blur-[6px] text-white cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-gtw07-c4 peer-focus-visible:outline-offset-[3px]">
<svg viewBox="0 0 24 24" class="w-5 h-5" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="sr-only">Toggle dark theme</span>
</label>
</div>
<header class="relative isolate overflow-hidden grid items-end min-h-[min(86svh,46rem)] bg-[radial-gradient(120%_90%_at_78%_6%,color-mix(in_oklab,var(--color-gtw07-c4)_55%,transparent)_0%,transparent_58%),linear-gradient(in_oklab_168deg,var(--color-gtw07-c1)_0%,var(--color-gtw07-c1)_14%,var(--color-gtw07-c2)_38%,var(--color-gtw07-c2)_48%,var(--color-gtw07-c3)_74%,var(--color-gtw07-c4)_100%)] after:content-[''] after:absolute after:inset-0 after:z-[1] after:pointer-events-none after:bg-[image:var(--gtw07-noise)] after:opacity-[var(--gtw07-dither)] after:mix-blend-overlay">
<div class="relative z-[2] w-[min(70rem,calc(100%-clamp(1.5rem,5vw,4rem)))] mx-auto py-[clamp(4rem,12vw,7rem)] grid gap-[0.85rem] max-w-[44rem]">
<p class="m-0 font-gtw07-mono text-[0.74rem] font-medium leading-none tracking-[0.18em] uppercase text-gtw07-ink">Dusk · 21:04 · clear</p>
<h1 id="gtw-07-title" class="m-0 font-gtw07-display text-[clamp(2.3rem,7vw,4.6rem)] font-semibold leading-[0.99] tracking-[-0.04em] text-balance">A gradient with nowhere to hide</h1>
<p class="m-0 max-w-[34rem] text-gtw07-ink text-[clamp(0.97rem,2vw,1.14rem)] text-pretty">Deep blue to violet across a full viewport is the hardest banding case on the web. This one is interpolated in <code class="font-gtw07-mono text-[0.9em]">oklab</code>, stopped three times through the dark third, and dithered with grain at 3.5%.</p>
<dl class="flex flex-wrap gap-2 mt-2 mb-0">
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Interpolation</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">in oklab</dd></div>
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Dither</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">0.035</dd></div>
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Stops</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">6</dd></div>
</dl>
</div>
</header>
<main class="w-[min(70rem,calc(100%-clamp(1.5rem,5vw,4rem)))] mx-auto py-[clamp(2.5rem,7vw,4.5rem)] grid gap-[clamp(2rem,5vw,3rem)]">
<section class="grid gap-4" aria-labelledby="gtw-07-h-ab">
<div class="grid gap-[0.35rem] pb-[0.7rem] border-b border-gtw07-line">
<h2 id="gtw-07-h-ab" class="m-0 font-gtw07-display text-[clamp(1.15rem,2.8vw,1.5rem)] font-semibold leading-tight tracking-[-0.02em]">The same ramp, treated and untreated</h2>
<p class="m-0 text-gtw07-mut text-[0.88rem] text-pretty">If you cannot see the difference, turn your screen brightness down — banding lives in the shadows.</p>
</div>
<div class="grid gap-[clamp(0.8rem,2vw,1.3rem)] grid-cols-[repeat(auto-fit,minmax(min(100%,17rem),1fr))]">
<figure class="m-0 grid gap-[0.6rem] min-w-0">
<div class="relative isolate overflow-hidden h-[clamp(11rem,26vw,17rem)] rounded-[14px] bg-[linear-gradient(168deg,var(--color-gtw07-c1),var(--color-gtw07-c3))]"></div>
<figcaption class="grid gap-[0.15rem]"><b class="font-gtw07-display text-[0.95rem] font-semibold leading-tight">Untreated</b><span class="font-gtw07-mono text-[0.8rem] leading-[1.4] text-gtw07-mut">sRGB interpolation, two stops, no dither</span></figcaption>
</figure>
<figure class="m-0 grid gap-[0.6rem] min-w-0">
<div class="relative isolate overflow-hidden h-[clamp(11rem,26vw,17rem)] rounded-[14px] bg-[linear-gradient(in_oklab_168deg,var(--color-gtw07-c1)_0%,var(--color-gtw07-c1)_12%,var(--color-gtw07-c2)_34%,var(--color-gtw07-c2)_46%,var(--color-gtw07-c3)_78%,var(--color-gtw07-c3)_100%)] after:content-[''] after:absolute after:inset-0 after:pointer-events-none after:bg-[image:var(--gtw07-noise)] after:opacity-[var(--gtw07-dither)] after:mix-blend-overlay"></div>
<figcaption class="grid gap-[0.15rem]"><b class="font-gtw07-display text-[0.95rem] font-semibold leading-tight">Treated</b><span class="font-gtw07-mono text-[0.8rem] leading-[1.4] text-gtw07-mut">oklab, six stops, 3.5% grain dither</span></figcaption>
</figure>
</div>
</section>
<section class="grid gap-[0.9rem] p-[clamp(1.2rem,3vw,1.9rem)] rounded-2xl border border-gtw07-line bg-gtw07-surf" aria-labelledby="gtw-07-h-steps">
<h2 id="gtw-07-h-steps" class="m-0 font-gtw07-display text-[clamp(1.15rem,2.8vw,1.5rem)] font-semibold leading-tight tracking-[-0.02em]">Ship it in this order</h2>
<ol class="m-0 pl-[1.3rem] grid gap-[0.55rem] text-gtw07-mut text-[0.92rem] marker:text-gtw07-c4">
<li><b class="font-semibold text-gtw07-ink">Interpolate in oklab.</b> Perceptually even steps mean smaller bands before you dither anything.</li>
<li><b class="font-semibold text-gtw07-ink">Add stops where it bands.</b> Almost always the darkest third. Two extra stops halve the step size.</li>
<li><b class="font-semibold text-gtw07-ink">Dither with grain at 0.03.</b> Raise in 0.005 increments until the bands go. Stop there.</li>
<li><b class="font-semibold text-gtw07-ink">Check on an OLED phone at low brightness.</b> The harshest banding test that exists, and the one nobody runs.</li>
</ol>
</section>
</main>
</section><section class="gtw-07 relative block w-full min-h-svh bg-gtw07-bg text-gtw07-ink" aria-labelledby="gtw-07-title">
<div class="absolute top-[clamp(1rem,3vw,1.75rem)] right-[clamp(0.85rem,3vw,1.75rem)] z-40">
<input type="checkbox" id="gtw-07-theme" class="gtw-07-theme peer sr-only">
<label for="gtw-07-theme" class="grid place-items-center w-11 h-11 rounded-full border border-white/30 bg-[oklch(0.15_0.03_280/0.4)] backdrop-blur-[6px] text-white cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-gtw07-c4 peer-focus-visible:outline-offset-[3px]">
<svg viewBox="0 0 24 24" class="w-5 h-5" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="sr-only">Toggle dark theme</span>
</label>
</div>
<header class="relative isolate overflow-hidden grid items-end min-h-[min(86svh,46rem)] bg-[radial-gradient(120%_90%_at_78%_6%,color-mix(in_oklab,var(--color-gtw07-c4)_55%,transparent)_0%,transparent_58%),linear-gradient(in_oklab_168deg,var(--color-gtw07-c1)_0%,var(--color-gtw07-c1)_14%,var(--color-gtw07-c2)_38%,var(--color-gtw07-c2)_48%,var(--color-gtw07-c3)_74%,var(--color-gtw07-c4)_100%)] after:content-[''] after:absolute after:inset-0 after:z-[1] after:pointer-events-none after:bg-[image:var(--gtw07-noise)] after:opacity-[var(--gtw07-dither)] after:mix-blend-overlay">
<div class="relative z-[2] w-[min(70rem,calc(100%-clamp(1.5rem,5vw,4rem)))] mx-auto py-[clamp(4rem,12vw,7rem)] grid gap-[0.85rem] max-w-[44rem]">
<p class="m-0 font-gtw07-mono text-[0.74rem] font-medium leading-none tracking-[0.18em] uppercase text-gtw07-ink">Dusk · 21:04 · clear</p>
<h1 id="gtw-07-title" class="m-0 font-gtw07-display text-[clamp(2.3rem,7vw,4.6rem)] font-semibold leading-[0.99] tracking-[-0.04em] text-balance">A gradient with nowhere to hide</h1>
<p class="m-0 max-w-[34rem] text-gtw07-ink text-[clamp(0.97rem,2vw,1.14rem)] text-pretty">Deep blue to violet across a full viewport is the hardest banding case on the web. This one is interpolated in <code class="font-gtw07-mono text-[0.9em]">oklab</code>, stopped three times through the dark third, and dithered with grain at 3.5%.</p>
<dl class="flex flex-wrap gap-2 mt-2 mb-0">
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Interpolation</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">in oklab</dd></div>
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Dither</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">0.035</dd></div>
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Stops</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">6</dd></div>
</dl>
</div>
</header>
<main class="w-[min(70rem,calc(100%-clamp(1.5rem,5vw,4rem)))] mx-auto py-[clamp(2.5rem,7vw,4.5rem)] grid gap-[clamp(2rem,5vw,3rem)]">
<section class="grid gap-4" aria-labelledby="gtw-07-h-ab">
<div class="grid gap-[0.35rem] pb-[0.7rem] border-b border-gtw07-line">
<h2 id="gtw-07-h-ab" class="m-0 font-gtw07-display text-[clamp(1.15rem,2.8vw,1.5rem)] font-semibold leading-tight tracking-[-0.02em]">The same ramp, treated and untreated</h2>
<p class="m-0 text-gtw07-mut text-[0.88rem] text-pretty">If you cannot see the difference, turn your screen brightness down — banding lives in the shadows.</p>
</div>
<div class="grid gap-[clamp(0.8rem,2vw,1.3rem)] grid-cols-[repeat(auto-fit,minmax(min(100%,17rem),1fr))]">
<figure class="m-0 grid gap-[0.6rem] min-w-0">
<div class="relative isolate overflow-hidden h-[clamp(11rem,26vw,17rem)] rounded-[14px] bg-[linear-gradient(168deg,var(--color-gtw07-c1),var(--color-gtw07-c3))]"></div>
<figcaption class="grid gap-[0.15rem]"><b class="font-gtw07-display text-[0.95rem] font-semibold leading-tight">Untreated</b><span class="font-gtw07-mono text-[0.8rem] leading-[1.4] text-gtw07-mut">sRGB interpolation, two stops, no dither</span></figcaption>
</figure>
<figure class="m-0 grid gap-[0.6rem] min-w-0">
<div class="relative isolate overflow-hidden h-[clamp(11rem,26vw,17rem)] rounded-[14px] bg-[linear-gradient(in_oklab_168deg,var(--color-gtw07-c1)_0%,var(--color-gtw07-c1)_12%,var(--color-gtw07-c2)_34%,var(--color-gtw07-c2)_46%,var(--color-gtw07-c3)_78%,var(--color-gtw07-c3)_100%)] after:content-[''] after:absolute after:inset-0 after:pointer-events-none after:bg-[image:var(--gtw07-noise)] after:opacity-[var(--gtw07-dither)] after:mix-blend-overlay"></div>
<figcaption class="grid gap-[0.15rem]"><b class="font-gtw07-display text-[0.95rem] font-semibold leading-tight">Treated</b><span class="font-gtw07-mono text-[0.8rem] leading-[1.4] text-gtw07-mut">oklab, six stops, 3.5% grain dither</span></figcaption>
</figure>
</div>
</section>
<section class="grid gap-[0.9rem] p-[clamp(1.2rem,3vw,1.9rem)] rounded-2xl border border-gtw07-line bg-gtw07-surf" aria-labelledby="gtw-07-h-steps">
<h2 id="gtw-07-h-steps" class="m-0 font-gtw07-display text-[clamp(1.15rem,2.8vw,1.5rem)] font-semibold leading-tight tracking-[-0.02em]">Ship it in this order</h2>
<ol class="m-0 pl-[1.3rem] grid gap-[0.55rem] text-gtw07-mut text-[0.92rem] marker:text-gtw07-c4">
<li><b class="font-semibold text-gtw07-ink">Interpolate in oklab.</b> Perceptually even steps mean smaller bands before you dither anything.</li>
<li><b class="font-semibold text-gtw07-ink">Add stops where it bands.</b> Almost always the darkest third. Two extra stops halve the step size.</li>
<li><b class="font-semibold text-gtw07-ink">Dither with grain at 0.03.</b> Raise in 0.005 increments until the bands go. Stop there.</li>
<li><b class="font-semibold text-gtw07-ink">Check on an OLED phone at low brightness.</b> The harshest banding test that exists, and the one nobody runs.</li>
</ol>
</section>
</main>
</section>@import "tailwindcss";
@theme {
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--font-gtw07-display: 'Space Grotesk', 'Inter Tight', ui-sans-serif, system-ui, sans-serif;
--font-gtw07-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}
.gtw-07 {
width: 100%;
min-height: 100svh;
--gtw07-dither: 0.035;
--gtw07-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='gtw07n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23gtw07n)'/%3E%3C/svg%3E");
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
line-height: 1.55;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
@media (prefers-color-scheme: light) {
.gtw-07 {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--gtw07-dither: 0.035;
}
}
@media (prefers-color-scheme: dark) {
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
}@import "tailwindcss";
@theme {
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--font-gtw07-display: 'Space Grotesk', 'Inter Tight', ui-sans-serif, system-ui, sans-serif;
--font-gtw07-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}
.gtw-07 {
width: 100%;
min-height: 100svh;
--gtw07-dither: 0.035;
--gtw07-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='gtw07n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23gtw07n)'/%3E%3C/svg%3E");
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
line-height: 1.55;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
@media (prefers-color-scheme: light) {
.gtw-07 {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--gtw07-dither: 0.035;
}
}
@media (prefers-color-scheme: dark) {
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
}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-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--font-gtw07-display: 'Space Grotesk', 'Inter Tight', ui-sans-serif, system-ui, sans-serif;
--font-gtw07-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}
.gtw-07 {
width: 100%;
min-height: 100svh;
--gtw07-dither: 0.035;
--gtw07-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='gtw07n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23gtw07n)'/%3E%3C/svg%3E");
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
line-height: 1.55;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
@media (prefers-color-scheme: light) {
.gtw-07 {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--gtw07-dither: 0.035;
}
}
@media (prefers-color-scheme: dark) {
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
}@import "tailwindcss";
@theme {
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--font-gtw07-display: 'Space Grotesk', 'Inter Tight', ui-sans-serif, system-ui, sans-serif;
--font-gtw07-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}
.gtw-07 {
width: 100%;
min-height: 100svh;
--gtw07-dither: 0.035;
--gtw07-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='gtw07n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23gtw07n)'/%3E%3C/svg%3E");
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
line-height: 1.55;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
@media (prefers-color-scheme: light) {
.gtw-07 {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--gtw07-dither: 0.035;
}
}
@media (prefers-color-scheme: dark) {
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
}<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+Grotesk:wght@400;500;600;700&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=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The gradient is an arbitrary value, not a
bg-linear-*utility, because Tailwind's gradient utilities have noin oklabform and the interpolation space is the point of the demo. This is the collection's clearest example of when NOT to force a utility. - Dither strength stays
--gtw07-dither(0.035 dark / 0.03 light) rather than a staticafter:opacity-[0.035]: the two themes genuinely need different numbers. marker:text-gtw07-c4replaces::marker { color: … }, which is the kind of one-line rule that used to be the reason a demo needed a stylesheet at all.- UI change from the CSS original: hero copy is now full-opacity ink over the ramp instead of
color-mix-faded text, and the glass chips carry solid white labels. The faded version measured under 4.5:1 against the brightest part of the gradient.
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 Anti-Banding Grainy CSS Gradient Hero
Source: https://codefronts.com/design-styles/tailwind-css-grain-texture/tailwind-anti-banding-grainy-css-gradient-hero/
Banding is not a bug in your gradient, it is the 8-bit display pipeline showing through — and the fix is the one film and print have used for a century: add noise. A full-bleed dusk hero interpolated in oklab, stopped through the dark third, dithered at 3.5%, plus a side-by-side of the same ramp treated and untreated.
## HTML
```html
<section class="gtw-07 relative block w-full min-h-svh bg-gtw07-bg text-gtw07-ink" aria-labelledby="gtw-07-title">
<div class="absolute top-[clamp(1rem,3vw,1.75rem)] right-[clamp(0.85rem,3vw,1.75rem)] z-40">
<input type="checkbox" id="gtw-07-theme" class="gtw-07-theme peer sr-only">
<label for="gtw-07-theme" class="grid place-items-center w-11 h-11 rounded-full border border-white/30 bg-[oklch(0.15_0.03_280/0.4)] backdrop-blur-[6px] text-white cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-gtw07-c4 peer-focus-visible:outline-offset-[3px]">
<svg viewBox="0 0 24 24" class="w-5 h-5" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="sr-only">Toggle dark theme</span>
</label>
</div>
<header class="relative isolate overflow-hidden grid items-end min-h-[min(86svh,46rem)] bg-[radial-gradient(120%_90%_at_78%_6%,color-mix(in_oklab,var(--color-gtw07-c4)_55%,transparent)_0%,transparent_58%),linear-gradient(in_oklab_168deg,var(--color-gtw07-c1)_0%,var(--color-gtw07-c1)_14%,var(--color-gtw07-c2)_38%,var(--color-gtw07-c2)_48%,var(--color-gtw07-c3)_74%,var(--color-gtw07-c4)_100%)] after:content-[''] after:absolute after:inset-0 after:z-[1] after:pointer-events-none after:bg-[image:var(--gtw07-noise)] after:opacity-[var(--gtw07-dither)] after:mix-blend-overlay">
<div class="relative z-[2] w-[min(70rem,calc(100%-clamp(1.5rem,5vw,4rem)))] mx-auto py-[clamp(4rem,12vw,7rem)] grid gap-[0.85rem] max-w-[44rem]">
<p class="m-0 font-gtw07-mono text-[0.74rem] font-medium leading-none tracking-[0.18em] uppercase text-gtw07-ink">Dusk · 21:04 · clear</p>
<h1 id="gtw-07-title" class="m-0 font-gtw07-display text-[clamp(2.3rem,7vw,4.6rem)] font-semibold leading-[0.99] tracking-[-0.04em] text-balance">A gradient with nowhere to hide</h1>
<p class="m-0 max-w-[34rem] text-gtw07-ink text-[clamp(0.97rem,2vw,1.14rem)] text-pretty">Deep blue to violet across a full viewport is the hardest banding case on the web. This one is interpolated in <code class="font-gtw07-mono text-[0.9em]">oklab</code>, stopped three times through the dark third, and dithered with grain at 3.5%.</p>
<dl class="flex flex-wrap gap-2 mt-2 mb-0">
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Interpolation</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">in oklab</dd></div>
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Dither</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">0.035</dd></div>
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Stops</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">6</dd></div>
</dl>
</div>
</header>
<main class="w-[min(70rem,calc(100%-clamp(1.5rem,5vw,4rem)))] mx-auto py-[clamp(2.5rem,7vw,4.5rem)] grid gap-[clamp(2rem,5vw,3rem)]">
<section class="grid gap-4" aria-labelledby="gtw-07-h-ab">
<div class="grid gap-[0.35rem] pb-[0.7rem] border-b border-gtw07-line">
<h2 id="gtw-07-h-ab" class="m-0 font-gtw07-display text-[clamp(1.15rem,2.8vw,1.5rem)] font-semibold leading-tight tracking-[-0.02em]">The same ramp, treated and untreated</h2>
<p class="m-0 text-gtw07-mut text-[0.88rem] text-pretty">If you cannot see the difference, turn your screen brightness down — banding lives in the shadows.</p>
</div>
<div class="grid gap-[clamp(0.8rem,2vw,1.3rem)] grid-cols-[repeat(auto-fit,minmax(min(100%,17rem),1fr))]">
<figure class="m-0 grid gap-[0.6rem] min-w-0">
<div class="relative isolate overflow-hidden h-[clamp(11rem,26vw,17rem)] rounded-[14px] bg-[linear-gradient(168deg,var(--color-gtw07-c1),var(--color-gtw07-c3))]"></div>
<figcaption class="grid gap-[0.15rem]"><b class="font-gtw07-display text-[0.95rem] font-semibold leading-tight">Untreated</b><span class="font-gtw07-mono text-[0.8rem] leading-[1.4] text-gtw07-mut">sRGB interpolation, two stops, no dither</span></figcaption>
</figure>
<figure class="m-0 grid gap-[0.6rem] min-w-0">
<div class="relative isolate overflow-hidden h-[clamp(11rem,26vw,17rem)] rounded-[14px] bg-[linear-gradient(in_oklab_168deg,var(--color-gtw07-c1)_0%,var(--color-gtw07-c1)_12%,var(--color-gtw07-c2)_34%,var(--color-gtw07-c2)_46%,var(--color-gtw07-c3)_78%,var(--color-gtw07-c3)_100%)] after:content-[''] after:absolute after:inset-0 after:pointer-events-none after:bg-[image:var(--gtw07-noise)] after:opacity-[var(--gtw07-dither)] after:mix-blend-overlay"></div>
<figcaption class="grid gap-[0.15rem]"><b class="font-gtw07-display text-[0.95rem] font-semibold leading-tight">Treated</b><span class="font-gtw07-mono text-[0.8rem] leading-[1.4] text-gtw07-mut">oklab, six stops, 3.5% grain dither</span></figcaption>
</figure>
</div>
</section>
<section class="grid gap-[0.9rem] p-[clamp(1.2rem,3vw,1.9rem)] rounded-2xl border border-gtw07-line bg-gtw07-surf" aria-labelledby="gtw-07-h-steps">
<h2 id="gtw-07-h-steps" class="m-0 font-gtw07-display text-[clamp(1.15rem,2.8vw,1.5rem)] font-semibold leading-tight tracking-[-0.02em]">Ship it in this order</h2>
<ol class="m-0 pl-[1.3rem] grid gap-[0.55rem] text-gtw07-mut text-[0.92rem] marker:text-gtw07-c4">
<li><b class="font-semibold text-gtw07-ink">Interpolate in oklab.</b> Perceptually even steps mean smaller bands before you dither anything.</li>
<li><b class="font-semibold text-gtw07-ink">Add stops where it bands.</b> Almost always the darkest third. Two extra stops halve the step size.</li>
<li><b class="font-semibold text-gtw07-ink">Dither with grain at 0.03.</b> Raise in 0.005 increments until the bands go. Stop there.</li>
<li><b class="font-semibold text-gtw07-ink">Check on an OLED phone at low brightness.</b> The harshest banding test that exists, and the one nobody runs.</li>
</ol>
</section>
</main>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--font-gtw07-display: 'Space Grotesk', 'Inter Tight', ui-sans-serif, system-ui, sans-serif;
--font-gtw07-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}
.gtw-07 {
width: 100%;
min-height: 100svh;
--gtw07-dither: 0.035;
--gtw07-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='gtw07n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23gtw07n)'/%3E%3C/svg%3E");
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
line-height: 1.55;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
@media (prefers-color-scheme: light) {
.gtw-07 {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--gtw07-dither: 0.035;
}
}
@media (prefers-color-scheme: dark) {
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
}
```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 Anti-Banding Grainy CSS Gradient Hero
Source: https://codefronts.com/design-styles/tailwind-css-grain-texture/tailwind-anti-banding-grainy-css-gradient-hero/
Banding is not a bug in your gradient, it is the 8-bit display pipeline showing through — and the fix is the one film and print have used for a century: add noise. A full-bleed dusk hero interpolated in oklab, stopped through the dark third, dithered at 3.5%, plus a side-by-side of the same ramp treated and untreated.
## HTML
```html
<section class="gtw-07 relative block w-full min-h-svh bg-gtw07-bg text-gtw07-ink" aria-labelledby="gtw-07-title">
<div class="absolute top-[clamp(1rem,3vw,1.75rem)] right-[clamp(0.85rem,3vw,1.75rem)] z-40">
<input type="checkbox" id="gtw-07-theme" class="gtw-07-theme peer sr-only">
<label for="gtw-07-theme" class="grid place-items-center w-11 h-11 rounded-full border border-white/30 bg-[oklch(0.15_0.03_280/0.4)] backdrop-blur-[6px] text-white cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-gtw07-c4 peer-focus-visible:outline-offset-[3px]">
<svg viewBox="0 0 24 24" class="w-5 h-5" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="sr-only">Toggle dark theme</span>
</label>
</div>
<header class="relative isolate overflow-hidden grid items-end min-h-[min(86svh,46rem)] bg-[radial-gradient(120%_90%_at_78%_6%,color-mix(in_oklab,var(--color-gtw07-c4)_55%,transparent)_0%,transparent_58%),linear-gradient(in_oklab_168deg,var(--color-gtw07-c1)_0%,var(--color-gtw07-c1)_14%,var(--color-gtw07-c2)_38%,var(--color-gtw07-c2)_48%,var(--color-gtw07-c3)_74%,var(--color-gtw07-c4)_100%)] after:content-[''] after:absolute after:inset-0 after:z-[1] after:pointer-events-none after:bg-[image:var(--gtw07-noise)] after:opacity-[var(--gtw07-dither)] after:mix-blend-overlay">
<div class="relative z-[2] w-[min(70rem,calc(100%-clamp(1.5rem,5vw,4rem)))] mx-auto py-[clamp(4rem,12vw,7rem)] grid gap-[0.85rem] max-w-[44rem]">
<p class="m-0 font-gtw07-mono text-[0.74rem] font-medium leading-none tracking-[0.18em] uppercase text-gtw07-ink">Dusk · 21:04 · clear</p>
<h1 id="gtw-07-title" class="m-0 font-gtw07-display text-[clamp(2.3rem,7vw,4.6rem)] font-semibold leading-[0.99] tracking-[-0.04em] text-balance">A gradient with nowhere to hide</h1>
<p class="m-0 max-w-[34rem] text-gtw07-ink text-[clamp(0.97rem,2vw,1.14rem)] text-pretty">Deep blue to violet across a full viewport is the hardest banding case on the web. This one is interpolated in <code class="font-gtw07-mono text-[0.9em]">oklab</code>, stopped three times through the dark third, and dithered with grain at 3.5%.</p>
<dl class="flex flex-wrap gap-2 mt-2 mb-0">
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Interpolation</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">in oklab</dd></div>
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Dither</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">0.035</dd></div>
<div class="grid gap-[0.1rem] px-[0.85rem] py-[0.55rem] rounded-[10px] border border-white/25 bg-[oklch(0.12_0.03_280/0.35)] backdrop-blur-[4px]"><dt class="m-0 font-gtw07-mono text-[0.68rem] leading-none tracking-[0.1em] uppercase text-white/80">Stops</dt><dd class="m-0 font-gtw07-mono text-[0.88rem] font-medium leading-tight text-white">6</dd></div>
</dl>
</div>
</header>
<main class="w-[min(70rem,calc(100%-clamp(1.5rem,5vw,4rem)))] mx-auto py-[clamp(2.5rem,7vw,4.5rem)] grid gap-[clamp(2rem,5vw,3rem)]">
<section class="grid gap-4" aria-labelledby="gtw-07-h-ab">
<div class="grid gap-[0.35rem] pb-[0.7rem] border-b border-gtw07-line">
<h2 id="gtw-07-h-ab" class="m-0 font-gtw07-display text-[clamp(1.15rem,2.8vw,1.5rem)] font-semibold leading-tight tracking-[-0.02em]">The same ramp, treated and untreated</h2>
<p class="m-0 text-gtw07-mut text-[0.88rem] text-pretty">If you cannot see the difference, turn your screen brightness down — banding lives in the shadows.</p>
</div>
<div class="grid gap-[clamp(0.8rem,2vw,1.3rem)] grid-cols-[repeat(auto-fit,minmax(min(100%,17rem),1fr))]">
<figure class="m-0 grid gap-[0.6rem] min-w-0">
<div class="relative isolate overflow-hidden h-[clamp(11rem,26vw,17rem)] rounded-[14px] bg-[linear-gradient(168deg,var(--color-gtw07-c1),var(--color-gtw07-c3))]"></div>
<figcaption class="grid gap-[0.15rem]"><b class="font-gtw07-display text-[0.95rem] font-semibold leading-tight">Untreated</b><span class="font-gtw07-mono text-[0.8rem] leading-[1.4] text-gtw07-mut">sRGB interpolation, two stops, no dither</span></figcaption>
</figure>
<figure class="m-0 grid gap-[0.6rem] min-w-0">
<div class="relative isolate overflow-hidden h-[clamp(11rem,26vw,17rem)] rounded-[14px] bg-[linear-gradient(in_oklab_168deg,var(--color-gtw07-c1)_0%,var(--color-gtw07-c1)_12%,var(--color-gtw07-c2)_34%,var(--color-gtw07-c2)_46%,var(--color-gtw07-c3)_78%,var(--color-gtw07-c3)_100%)] after:content-[''] after:absolute after:inset-0 after:pointer-events-none after:bg-[image:var(--gtw07-noise)] after:opacity-[var(--gtw07-dither)] after:mix-blend-overlay"></div>
<figcaption class="grid gap-[0.15rem]"><b class="font-gtw07-display text-[0.95rem] font-semibold leading-tight">Treated</b><span class="font-gtw07-mono text-[0.8rem] leading-[1.4] text-gtw07-mut">oklab, six stops, 3.5% grain dither</span></figcaption>
</figure>
</div>
</section>
<section class="grid gap-[0.9rem] p-[clamp(1.2rem,3vw,1.9rem)] rounded-2xl border border-gtw07-line bg-gtw07-surf" aria-labelledby="gtw-07-h-steps">
<h2 id="gtw-07-h-steps" class="m-0 font-gtw07-display text-[clamp(1.15rem,2.8vw,1.5rem)] font-semibold leading-tight tracking-[-0.02em]">Ship it in this order</h2>
<ol class="m-0 pl-[1.3rem] grid gap-[0.55rem] text-gtw07-mut text-[0.92rem] marker:text-gtw07-c4">
<li><b class="font-semibold text-gtw07-ink">Interpolate in oklab.</b> Perceptually even steps mean smaller bands before you dither anything.</li>
<li><b class="font-semibold text-gtw07-ink">Add stops where it bands.</b> Almost always the darkest third. Two extra stops halve the step size.</li>
<li><b class="font-semibold text-gtw07-ink">Dither with grain at 0.03.</b> Raise in 0.005 increments until the bands go. Stop there.</li>
<li><b class="font-semibold text-gtw07-ink">Check on an OLED phone at low brightness.</b> The harshest banding test that exists, and the one nobody runs.</li>
</ol>
</section>
</main>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--font-gtw07-display: 'Space Grotesk', 'Inter Tight', ui-sans-serif, system-ui, sans-serif;
--font-gtw07-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}
.gtw-07 {
width: 100%;
min-height: 100svh;
--gtw07-dither: 0.035;
--gtw07-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='gtw07n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23gtw07n)'/%3E%3C/svg%3E");
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
line-height: 1.55;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
@media (prefers-color-scheme: light) {
.gtw-07 {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #08060f;
--color-gtw07-bg: oklch(0.13 0.02 285);
--color-gtw07-surf: #120f1d;
--color-gtw07-surf: oklch(0.19 0.03 285);
--color-gtw07-line: #262138;
--color-gtw07-line: oklch(0.29 0.04 285);
--color-gtw07-ink: #f2eefb;
--color-gtw07-ink: oklch(0.96 0.015 290);
--color-gtw07-mut: #a49bbd;
--color-gtw07-mut: oklch(0.71 0.04 290);
--color-gtw07-c1: #060b1f;
--color-gtw07-c1: oklch(0.18 0.06 268);
--color-gtw07-c2: #21215c;
--color-gtw07-c2: oklch(0.32 0.11 280);
--color-gtw07-c3: #6d3f8f;
--color-gtw07-c3: oklch(0.48 0.15 310);
--color-gtw07-c4: #d97757;
--color-gtw07-c4: oklch(0.7 0.13 40);
--gtw07-dither: 0.035;
}
}
@media (prefers-color-scheme: dark) {
.gtw-07:has(.gtw-07-theme:checked) {
--color-gtw07-bg: #f4f1fa;
--color-gtw07-bg: oklch(0.96 0.01 290);
--color-gtw07-surf: #ffffff;
--color-gtw07-surf: oklch(1 0 0);
--color-gtw07-line: #e2dcf0;
--color-gtw07-line: oklch(0.9 0.02 290);
--color-gtw07-ink: #120f1d;
--color-gtw07-ink: oklch(0.19 0.03 285);
--color-gtw07-mut: #625a7c;
--color-gtw07-mut: oklch(0.49 0.05 290);
--color-gtw07-c1: #dfd8f2;
--color-gtw07-c1: oklch(0.89 0.04 290);
--color-gtw07-c2: #a99ede;
--color-gtw07-c2: oklch(0.74 0.09 288);
--color-gtw07-c3: #c68fb8;
--color-gtw07-c3: oklch(0.72 0.1 340);
--color-gtw07-c4: #f0b083;
--color-gtw07-c4: oklch(0.82 0.1 55);
--gtw07-dither: 0.03;
}
}
```How this works
Why gradients band. An 8-bit channel has 256 steps. A gradient crossing 400 pixels with 12 available steps in the dark end repeats each colour for 33 pixels, and the eye reads those repeats as stripes — Mach banding exaggerates the edges further. Nothing about the CSS is wrong; the output precision is the limit.
Three fixes, in order of leverage. First interpolate in oklab: perceptually even steps mean smaller jumps before you do anything else. Second, add stops where it bands — almost always the darkest third; two extra stops halve the step size there. Third, dither with noise at about 0.03: the grain breaks the boundary between adjacent bands into a random edge the eye cannot resolve into a line.
The A/B is the argument. The untreated panel is sRGB interpolation, two stops, no dither — the default anyone would write. The treated panel differs in exactly the three ways above. On a good monitor at full brightness they look similar; turn the brightness down and the difference is unmissable, which is why the note says so.
Port specifics. The gradient's colour space cannot be a named utility (bg-linear-to-b has no in oklab form), so it is an arbitrary value — and that is the correct call: the interpolation space is part of this gradient's design, not a token in a scale. The dither strength stays a custom property because it differs per theme.
Make it yours
- Dither strength:
--gtw07-dither. Start at 0.03 and raise in 0.005 steps until the bands go, then stop — more grain is not a better fix. - Stops: add pairs in the darkest third first. A doubled stop (
var(--x)_38%,var(--x)_48%) holds a colour flat and forces the ramp elsewhere. - Angle:
168degreads as evening light. Anything past 180 inverts the horizon and the warm wash stops making sense. - Swap the four ramp tokens for your brand's dusk/dawn pair — the technique is independent of hue.
- Hero height is
min-h-[min(86svh,46rem)]; drop to70svhif the page has content that must be visible above the fold.
Gotchas — read before shipping
- Banding is display-dependent. A gradient that looks clean on a 10-bit monitor can band badly on an 8-bit laptop panel — test on the cheap screen, not the good one.
- Dither opacity above about 0.06 stops hiding bands and starts reading as texture. If you need that much, your ramp needs more stops instead.
in oklabinsidebg-[…]must not contain unescaped spaces — writein_oklab_168deg. A space there breaks the whole class silently.- The noise layer needs
z-indexbelow the copy andpointer-events-none; the hero's text sits atz-[2]on purpose. - Check the treated hero on an OLED phone at low brightness. It is the harshest banding test that exists and the one nobody runs.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Gradient interpolation with in oklab shipped in Chrome 111, Safari 16.2 and Firefox 113 — below that the gradient renders in sRGB and the dither still helps, so the degradation is graceful. The quoted floor is Tailwind v4's own sheet.