03 / 20
All 20 in 20 CSS Retro UI
- 01CRT Scanlines and Screen Curvature
- 02Pixel Art Scaling with Image Rendering
- 03Dithering Patterns with CSS Gradients
- 04One Bit Bevel Borders with Box Shadow
- 05Classic Mac System 7 Window Chrome
- 06MS DOS Text Mode Interface Grid
- 07Teletext and Ceefax Block Graphics
- 08Amber and Green Phosphor Monitor
- 09Windows 95 Desktop Interface
- 10Retro Futuristic Terminal Interface
- 11Eight Bit Pixel Art Buttons and HUD
- 12Neo Brutalist Component Board
- 13Skeuomorphic Cassette Player
- 14Vaporwave Aesthetic Scene
- 15Retro Arcade Game Interface
- 16ASCII Art Terminal Interface
- 17Nineties Web Guestbook Widget
- 18Y2K Aqua Gloss Interface Kit
- 19Vintage Storefront Layout
- 20Retro Desktop Portfolio Template
Dithering Patterns with CSS Gradients
Ordered dithering is how a limited palette fakes colours it does not have: alternate two available colours in a fixed pattern and the eye averages them. Built here from layered repeating-conic-gradient checkerboards over flat fills, with a smooth gradient beside Bayer ramps at 2, 4 and 8 colours — and an honest statement of where the CSS approximation stops.
Untitled UI Pro: a 10,000+ component Figma UI kit · from $129 one-time
Untitled UI · Affiliate link
The code
<section class="rt-03" aria-labelledby="rt-03-title">
<h2 class="rt-03__sr" id="rt-03-title">Ordered dithering ramps built from CSS gradients</h2>
<div class="rt-03__theme">
<input class="rt-03__theme-input" type="checkbox" id="rt-03-theme">
<label class="rt-03__theme-label" for="rt-03-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
<span class="rt-03__sr">Toggle dark surround</span>
</label>
</div>
<div class="rt-03__stage">
<header class="rt-03__head">
<p class="rt-03__kicker">Palette · Game Boy DMG-01 · 4 tones, exact sRGB</p>
<h3 class="rt-03__h">Four colours, twelve apparent tones</h3>
<p class="rt-03__sub">Every band below contains only palette colours. The intermediate tones are patterns, not blends.</p>
</header>
<div class="rt-03__swatches">
<span class="rt-03__sw rt-03__sw--0">#0f380f</span>
<span class="rt-03__sw rt-03__sw--1">#306230</span>
<span class="rt-03__sw rt-03__sw--2">#8bac0f</span>
<span class="rt-03__sw rt-03__sw--3">#9bbc0f</span>
</div>
<div class="rt-03__ramps">
<article class="rt-03__ramp" aria-labelledby="rt-03-r0">
<h4 class="rt-03__ramp-h" id="rt-03-r0">Reference · unlimited colour</h4>
<div class="rt-03__bar rt-03__bar--smooth" aria-hidden="true"></div>
<p class="rt-03__cap">A plain linear-gradient. Contains thousands of colours the hardware never had.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r1">
<h4 class="rt-03__ramp-h" id="rt-03-r1">2 colours · 2×2 Bayer</h4>
<div class="rt-03__bar" aria-hidden="true">
<span class="rt-03__band rt-03__band--a0"></span>
<span class="rt-03__band rt-03__band--a25"></span>
<span class="rt-03__band rt-03__band--a50"></span>
<span class="rt-03__band rt-03__band--a75"></span>
<span class="rt-03__band rt-03__band--a100"></span>
</div>
<p class="rt-03__cap">Darkest and lightest tone only. Five apparent steps from two real ones.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r2">
<h4 class="rt-03__ramp-h" id="rt-03-r2">4 colours · adjacent-pair dither</h4>
<div class="rt-03__bar" aria-hidden="true">
<span class="rt-03__band rt-03__band--b0"></span>
<span class="rt-03__band rt-03__band--b1"></span>
<span class="rt-03__band rt-03__band--b2"></span>
<span class="rt-03__band rt-03__band--b3"></span>
<span class="rt-03__band rt-03__band--b4"></span>
<span class="rt-03__band rt-03__band--b5"></span>
<span class="rt-03__band rt-03__band--b6"></span>
</div>
<p class="rt-03__cap">Each pattern mixes only its two neighbouring palette entries.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r3">
<h4 class="rt-03__ramp-h" id="rt-03-r3">8 steps · 4px cell, fine mask</h4>
<div class="rt-03__bar rt-03__bar--fine" aria-hidden="true">
<span class="rt-03__band rt-03__band--b0"></span>
<span class="rt-03__band rt-03__band--b1"></span>
<span class="rt-03__band rt-03__band--b2"></span>
<span class="rt-03__band rt-03__band--b3"></span>
<span class="rt-03__band rt-03__band--b4"></span>
<span class="rt-03__band rt-03__band--b5"></span>
<span class="rt-03__band rt-03__band--b6"></span>
<span class="rt-03__band rt-03__band--a100"></span>
</div>
<p class="rt-03__cap">Same construction at a 2px cell. Closer to a blend, further from a period-correct look.</p>
</article>
</div>
<aside class="rt-03__panel">
<h4 class="rt-03__panel-h">Coverage table</h4>
<dl class="rt-03__dl">
<div><dt>0%</dt><dd>flat base colour, no overlay</dd></div>
<div><dt>25%</dt><dd>one quadrant of the 2×2 cell</dd></div>
<div><dt>50%</dt><dd>two opposite quadrants — checkerboard</dd></div>
<div><dt>75%</dt><dd>three quadrants, inverse of 25%</dd></div>
<div><dt>100%</dt><dd>flat next palette colour</dd></div>
</dl>
</aside>
</div>
</section><section class="rt-03" aria-labelledby="rt-03-title">
<h2 class="rt-03__sr" id="rt-03-title">Ordered dithering ramps built from CSS gradients</h2>
<div class="rt-03__theme">
<input class="rt-03__theme-input" type="checkbox" id="rt-03-theme">
<label class="rt-03__theme-label" for="rt-03-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
<span class="rt-03__sr">Toggle dark surround</span>
</label>
</div>
<div class="rt-03__stage">
<header class="rt-03__head">
<p class="rt-03__kicker">Palette · Game Boy DMG-01 · 4 tones, exact sRGB</p>
<h3 class="rt-03__h">Four colours, twelve apparent tones</h3>
<p class="rt-03__sub">Every band below contains only palette colours. The intermediate tones are patterns, not blends.</p>
</header>
<div class="rt-03__swatches">
<span class="rt-03__sw rt-03__sw--0">#0f380f</span>
<span class="rt-03__sw rt-03__sw--1">#306230</span>
<span class="rt-03__sw rt-03__sw--2">#8bac0f</span>
<span class="rt-03__sw rt-03__sw--3">#9bbc0f</span>
</div>
<div class="rt-03__ramps">
<article class="rt-03__ramp" aria-labelledby="rt-03-r0">
<h4 class="rt-03__ramp-h" id="rt-03-r0">Reference · unlimited colour</h4>
<div class="rt-03__bar rt-03__bar--smooth" aria-hidden="true"></div>
<p class="rt-03__cap">A plain linear-gradient. Contains thousands of colours the hardware never had.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r1">
<h4 class="rt-03__ramp-h" id="rt-03-r1">2 colours · 2×2 Bayer</h4>
<div class="rt-03__bar" aria-hidden="true">
<span class="rt-03__band rt-03__band--a0"></span>
<span class="rt-03__band rt-03__band--a25"></span>
<span class="rt-03__band rt-03__band--a50"></span>
<span class="rt-03__band rt-03__band--a75"></span>
<span class="rt-03__band rt-03__band--a100"></span>
</div>
<p class="rt-03__cap">Darkest and lightest tone only. Five apparent steps from two real ones.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r2">
<h4 class="rt-03__ramp-h" id="rt-03-r2">4 colours · adjacent-pair dither</h4>
<div class="rt-03__bar" aria-hidden="true">
<span class="rt-03__band rt-03__band--b0"></span>
<span class="rt-03__band rt-03__band--b1"></span>
<span class="rt-03__band rt-03__band--b2"></span>
<span class="rt-03__band rt-03__band--b3"></span>
<span class="rt-03__band rt-03__band--b4"></span>
<span class="rt-03__band rt-03__band--b5"></span>
<span class="rt-03__band rt-03__band--b6"></span>
</div>
<p class="rt-03__cap">Each pattern mixes only its two neighbouring palette entries.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r3">
<h4 class="rt-03__ramp-h" id="rt-03-r3">8 steps · 4px cell, fine mask</h4>
<div class="rt-03__bar rt-03__bar--fine" aria-hidden="true">
<span class="rt-03__band rt-03__band--b0"></span>
<span class="rt-03__band rt-03__band--b1"></span>
<span class="rt-03__band rt-03__band--b2"></span>
<span class="rt-03__band rt-03__band--b3"></span>
<span class="rt-03__band rt-03__band--b4"></span>
<span class="rt-03__band rt-03__band--b5"></span>
<span class="rt-03__band rt-03__band--b6"></span>
<span class="rt-03__band rt-03__band--a100"></span>
</div>
<p class="rt-03__cap">Same construction at a 2px cell. Closer to a blend, further from a period-correct look.</p>
</article>
</div>
<aside class="rt-03__panel">
<h4 class="rt-03__panel-h">Coverage table</h4>
<dl class="rt-03__dl">
<div><dt>0%</dt><dd>flat base colour, no overlay</dd></div>
<div><dt>25%</dt><dd>one quadrant of the 2×2 cell</dd></div>
<div><dt>50%</dt><dd>two opposite quadrants — checkerboard</dd></div>
<div><dt>75%</dt><dd>three quadrants, inverse of 25%</dd></div>
<div><dt>100%</dt><dd>flat next palette colour</dd></div>
</dl>
</aside>
</div>
</section>.rt-03 {
--rt-03-cell: 4px;
--rt-03-p0: #0f380f;
--rt-03-p1: #306230;
--rt-03-p2: #8bac0f;
--rt-03-p3: #9bbc0f;
--rt-03-cond: ui-sans-serif, "Helvetica Neue", "Arial Narrow", Helvetica, Arial, sans-serif;
--rt-03-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--rt-03-bg: #f2efe6;
--rt-03-bg: oklch(95% 0.012 100);
--rt-03-surface: #ffffff;
--rt-03-surface: oklch(100% 0 0);
--rt-03-ink: #15170f;
--rt-03-ink: oklch(19% 0.012 120);
--rt-03-ink-2: #4e5344;
--rt-03-ink-2: oklch(45% 0.017 120);
--rt-03-line: #b6b9a6;
--rt-03-line: oklch(77% 0.02 118);
--rt-03-acc: #2f6b12;
--rt-03-acc: oklch(46% 0.14 133);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--rt-03-bg);
color: var(--rt-03-ink);
font-family: var(--rt-03-cond);
-webkit-text-size-adjust: 100%;
}
.rt-03 *,
.rt-03 *::before,
.rt-03 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.rt-03 {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
}
.rt-03[data-theme="dark"],
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
@media (prefers-color-scheme: light) {
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
}
@media (prefers-color-scheme: dark) {
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #f2efe6;
--rt-03-bg: oklch(95% 0.012 100);
--rt-03-surface: #ffffff;
--rt-03-surface: oklch(100% 0 0);
--rt-03-ink: #15170f;
--rt-03-ink: oklch(19% 0.012 120);
--rt-03-ink-2: #4e5344;
--rt-03-ink-2: oklch(45% 0.017 120);
--rt-03-line: #b6b9a6;
--rt-03-line: oklch(77% 0.02 118);
--rt-03-acc: #2f6b12;
--rt-03-acc: oklch(46% 0.14 133);
}
}
.rt-03__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.rt-03__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 6;
}
.rt-03__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.rt-03__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--rt-03-line);
border-radius: 50%;
background: var(--rt-03-surface);
color: var(--rt-03-ink-2);
cursor: pointer;
}
.rt-03__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.rt-03__theme-input:focus-visible + .rt-03__theme-label {
outline: 2px solid var(--rt-03-acc);
outline-offset: 2px;
}
.rt-03__stage {
max-inline-size: 68rem;
margin-inline: auto;
padding: clamp(3.5rem, 3rem + 3vw, 5rem) clamp(0.85rem, 0.5rem + 2vw, 2rem) 4rem;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1.5rem, 1rem + 2vw, 2.25rem);
}
.rt-03__head {
display: grid;
gap: 0.45rem;
min-inline-size: 0;
}
.rt-03__kicker {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.68rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
}
.rt-03__h {
margin: 0;
font-size: clamp(1.35rem, 1rem + 2vw, 2.2rem);
line-height: 1.08;
letter-spacing: -0.03em;
font-weight: 700;
text-wrap: balance;
min-inline-size: 0;
}
.rt-03__sub {
margin: 0;
font-size: 0.95rem;
line-height: 1.55;
color: var(--rt-03-ink-2);
max-inline-size: 52ch;
text-wrap: pretty;
min-inline-size: 0;
}
.rt-03__swatches {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.rt-03__sw {
min-inline-size: 0;
flex: none;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
padding: 0.55rem 0.7rem;
border: 1px solid var(--rt-03-line);
letter-spacing: 0.03em;
}
.rt-03__sw--0 {
background: #0f380f;
color: #9bbc0f;
}
.rt-03__sw--1 {
background: #306230;
color: #dff0a8;
}
.rt-03__sw--2 {
background: #8bac0f;
color: #10290a;
}
.rt-03__sw--3 {
background: #9bbc0f;
color: #10290a;
}
.rt-03__ramps {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1.1rem;
}
.rt-03__ramp {
min-inline-size: 0;
display: grid;
gap: 0.6rem;
align-content: start;
background: var(--rt-03-surface);
border: 1px solid var(--rt-03-line);
padding: clamp(0.8rem, 0.6rem + 0.8vw, 1.1rem);
}
.rt-03__ramp-h {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
min-inline-size: 0;
}
.rt-03__bar {
display: flex;
block-size: clamp(64px, 8vw, 92px);
min-inline-size: 0;
border: 2px solid #0f380f;
overflow: hidden;
}
.rt-03__bar--smooth {
background: linear-gradient(90deg, #0f380f, #306230, #8bac0f, #9bbc0f);
}
.rt-03__band {
flex: 1 1 0;
min-inline-size: 0;
background-size: var(--rt-03-cell) var(--rt-03-cell);
}
.rt-03__bar--fine {
--rt-03-cell: 2px;
}
.rt-03__band--a0 {
background-color: var(--rt-03-p0);
}
.rt-03__band--a100 {
background-color: var(--rt-03-p3);
}
.rt-03__band--a25 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--a50 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p3) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--a75 {
background-color: var(--rt-03-p3);
background-image: repeating-conic-gradient(var(--rt-03-p0) 0% 25%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b0 {
background-color: var(--rt-03-p0);
}
.rt-03__band--b1 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p1) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p1) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b2 {
background-color: var(--rt-03-p1);
}
.rt-03__band--b3 {
background-color: var(--rt-03-p1);
background-image: repeating-conic-gradient(var(--rt-03-p2) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p2) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b4 {
background-color: var(--rt-03-p2);
}
.rt-03__band--b5 {
background-color: var(--rt-03-p2);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p3) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b6 {
background-color: var(--rt-03-p3);
}
@supports not (background: repeating-conic-gradient(#000 0% 25%, #fff 0% 50%)) {
.rt-03__band--a25,
.rt-03__band--a50,
.rt-03__band--a75,
.rt-03__band--b1,
.rt-03__band--b3,
.rt-03__band--b5 {
background-image: repeating-linear-gradient(45deg, currentColor 0 2px, rgba(0,0,0,0) 2px 4px);
}
.rt-03__band--a25,
.rt-03__band--a50 {
color: var(--rt-03-p3);
}
.rt-03__band--a75 {
color: var(--rt-03-p0);
}
.rt-03__band--b1 {
color: var(--rt-03-p1);
}
.rt-03__band--b3 {
color: var(--rt-03-p2);
}
.rt-03__band--b5 {
color: var(--rt-03-p3);
}
}
.rt-03__cap {
margin: 0;
font-size: 0.82rem;
line-height: 1.5;
color: var(--rt-03-ink-2);
min-inline-size: 0;
text-wrap: pretty;
}
.rt-03__panel {
min-inline-size: 0;
background: var(--rt-03-surface);
border: 1px solid var(--rt-03-line);
border-left: 4px solid var(--rt-03-acc);
padding: clamp(0.85rem, 0.6rem + 1vw, 1.2rem);
display: grid;
gap: 0.65rem;
}
.rt-03__panel-h {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
}
.rt-03__dl {
margin: 0;
display: grid;
gap: 0.3rem;
font-size: 0.85rem;
}
.rt-03__dl > div {
display: flex;
gap: 0.75rem;
align-items: baseline;
min-inline-size: 0;
}
.rt-03__dl dt {
margin: 0;
font-family: var(--rt-03-mono);
font-variant-numeric: tabular-nums;
inline-size: 3.5rem;
flex: none;
color: var(--rt-03-acc);
}
.rt-03__dl dd {
margin: 0;
color: var(--rt-03-ink-2);
min-inline-size: 0;
}
@media (min-width: 54rem) {
.rt-03__ramps {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}.rt-03 {
--rt-03-cell: 4px;
--rt-03-p0: #0f380f;
--rt-03-p1: #306230;
--rt-03-p2: #8bac0f;
--rt-03-p3: #9bbc0f;
--rt-03-cond: ui-sans-serif, "Helvetica Neue", "Arial Narrow", Helvetica, Arial, sans-serif;
--rt-03-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--rt-03-bg: #f2efe6;
--rt-03-bg: oklch(95% 0.012 100);
--rt-03-surface: #ffffff;
--rt-03-surface: oklch(100% 0 0);
--rt-03-ink: #15170f;
--rt-03-ink: oklch(19% 0.012 120);
--rt-03-ink-2: #4e5344;
--rt-03-ink-2: oklch(45% 0.017 120);
--rt-03-line: #b6b9a6;
--rt-03-line: oklch(77% 0.02 118);
--rt-03-acc: #2f6b12;
--rt-03-acc: oklch(46% 0.14 133);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--rt-03-bg);
color: var(--rt-03-ink);
font-family: var(--rt-03-cond);
-webkit-text-size-adjust: 100%;
}
.rt-03 *,
.rt-03 *::before,
.rt-03 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.rt-03 {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
}
.rt-03[data-theme="dark"],
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
@media (prefers-color-scheme: light) {
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
}
@media (prefers-color-scheme: dark) {
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #f2efe6;
--rt-03-bg: oklch(95% 0.012 100);
--rt-03-surface: #ffffff;
--rt-03-surface: oklch(100% 0 0);
--rt-03-ink: #15170f;
--rt-03-ink: oklch(19% 0.012 120);
--rt-03-ink-2: #4e5344;
--rt-03-ink-2: oklch(45% 0.017 120);
--rt-03-line: #b6b9a6;
--rt-03-line: oklch(77% 0.02 118);
--rt-03-acc: #2f6b12;
--rt-03-acc: oklch(46% 0.14 133);
}
}
.rt-03__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.rt-03__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 6;
}
.rt-03__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.rt-03__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--rt-03-line);
border-radius: 50%;
background: var(--rt-03-surface);
color: var(--rt-03-ink-2);
cursor: pointer;
}
.rt-03__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.rt-03__theme-input:focus-visible + .rt-03__theme-label {
outline: 2px solid var(--rt-03-acc);
outline-offset: 2px;
}
.rt-03__stage {
max-inline-size: 68rem;
margin-inline: auto;
padding: clamp(3.5rem, 3rem + 3vw, 5rem) clamp(0.85rem, 0.5rem + 2vw, 2rem) 4rem;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1.5rem, 1rem + 2vw, 2.25rem);
}
.rt-03__head {
display: grid;
gap: 0.45rem;
min-inline-size: 0;
}
.rt-03__kicker {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.68rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
}
.rt-03__h {
margin: 0;
font-size: clamp(1.35rem, 1rem + 2vw, 2.2rem);
line-height: 1.08;
letter-spacing: -0.03em;
font-weight: 700;
text-wrap: balance;
min-inline-size: 0;
}
.rt-03__sub {
margin: 0;
font-size: 0.95rem;
line-height: 1.55;
color: var(--rt-03-ink-2);
max-inline-size: 52ch;
text-wrap: pretty;
min-inline-size: 0;
}
.rt-03__swatches {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.rt-03__sw {
min-inline-size: 0;
flex: none;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
padding: 0.55rem 0.7rem;
border: 1px solid var(--rt-03-line);
letter-spacing: 0.03em;
}
.rt-03__sw--0 {
background: #0f380f;
color: #9bbc0f;
}
.rt-03__sw--1 {
background: #306230;
color: #dff0a8;
}
.rt-03__sw--2 {
background: #8bac0f;
color: #10290a;
}
.rt-03__sw--3 {
background: #9bbc0f;
color: #10290a;
}
.rt-03__ramps {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1.1rem;
}
.rt-03__ramp {
min-inline-size: 0;
display: grid;
gap: 0.6rem;
align-content: start;
background: var(--rt-03-surface);
border: 1px solid var(--rt-03-line);
padding: clamp(0.8rem, 0.6rem + 0.8vw, 1.1rem);
}
.rt-03__ramp-h {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
min-inline-size: 0;
}
.rt-03__bar {
display: flex;
block-size: clamp(64px, 8vw, 92px);
min-inline-size: 0;
border: 2px solid #0f380f;
overflow: hidden;
}
.rt-03__bar--smooth {
background: linear-gradient(90deg, #0f380f, #306230, #8bac0f, #9bbc0f);
}
.rt-03__band {
flex: 1 1 0;
min-inline-size: 0;
background-size: var(--rt-03-cell) var(--rt-03-cell);
}
.rt-03__bar--fine {
--rt-03-cell: 2px;
}
.rt-03__band--a0 {
background-color: var(--rt-03-p0);
}
.rt-03__band--a100 {
background-color: var(--rt-03-p3);
}
.rt-03__band--a25 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--a50 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p3) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--a75 {
background-color: var(--rt-03-p3);
background-image: repeating-conic-gradient(var(--rt-03-p0) 0% 25%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b0 {
background-color: var(--rt-03-p0);
}
.rt-03__band--b1 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p1) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p1) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b2 {
background-color: var(--rt-03-p1);
}
.rt-03__band--b3 {
background-color: var(--rt-03-p1);
background-image: repeating-conic-gradient(var(--rt-03-p2) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p2) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b4 {
background-color: var(--rt-03-p2);
}
.rt-03__band--b5 {
background-color: var(--rt-03-p2);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p3) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b6 {
background-color: var(--rt-03-p3);
}
@supports not (background: repeating-conic-gradient(#000 0% 25%, #fff 0% 50%)) {
.rt-03__band--a25,
.rt-03__band--a50,
.rt-03__band--a75,
.rt-03__band--b1,
.rt-03__band--b3,
.rt-03__band--b5 {
background-image: repeating-linear-gradient(45deg, currentColor 0 2px, rgba(0,0,0,0) 2px 4px);
}
.rt-03__band--a25,
.rt-03__band--a50 {
color: var(--rt-03-p3);
}
.rt-03__band--a75 {
color: var(--rt-03-p0);
}
.rt-03__band--b1 {
color: var(--rt-03-p1);
}
.rt-03__band--b3 {
color: var(--rt-03-p2);
}
.rt-03__band--b5 {
color: var(--rt-03-p3);
}
}
.rt-03__cap {
margin: 0;
font-size: 0.82rem;
line-height: 1.5;
color: var(--rt-03-ink-2);
min-inline-size: 0;
text-wrap: pretty;
}
.rt-03__panel {
min-inline-size: 0;
background: var(--rt-03-surface);
border: 1px solid var(--rt-03-line);
border-left: 4px solid var(--rt-03-acc);
padding: clamp(0.85rem, 0.6rem + 1vw, 1.2rem);
display: grid;
gap: 0.65rem;
}
.rt-03__panel-h {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
}
.rt-03__dl {
margin: 0;
display: grid;
gap: 0.3rem;
font-size: 0.85rem;
}
.rt-03__dl > div {
display: flex;
gap: 0.75rem;
align-items: baseline;
min-inline-size: 0;
}
.rt-03__dl dt {
margin: 0;
font-family: var(--rt-03-mono);
font-variant-numeric: tabular-nums;
inline-size: 3.5rem;
flex: none;
color: var(--rt-03-acc);
}
.rt-03__dl dd {
margin: 0;
color: var(--rt-03-ink-2);
min-inline-size: 0;
}
@media (min-width: 54rem) {
.rt-03__ramps {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}Here's a working CSS Retro UI 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: Dithering Patterns with CSS Gradients
Source: https://codefronts.com/design-styles/css-retro-designs/dithering-patterns-with-css-gradients/
Ordered dithering is how a limited palette fakes colours it does not have: alternate two available colours in a fixed pattern and the eye averages them. Built here from layered repeating-conic-gradient checkerboards over flat fills, with a smooth gradient beside Bayer ramps at 2, 4 and 8 colours — and an honest statement of where the CSS approximation stops.
## HTML
```html
<section class="rt-03" aria-labelledby="rt-03-title">
<h2 class="rt-03__sr" id="rt-03-title">Ordered dithering ramps built from CSS gradients</h2>
<div class="rt-03__theme">
<input class="rt-03__theme-input" type="checkbox" id="rt-03-theme">
<label class="rt-03__theme-label" for="rt-03-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
<span class="rt-03__sr">Toggle dark surround</span>
</label>
</div>
<div class="rt-03__stage">
<header class="rt-03__head">
<p class="rt-03__kicker">Palette · Game Boy DMG-01 · 4 tones, exact sRGB</p>
<h3 class="rt-03__h">Four colours, twelve apparent tones</h3>
<p class="rt-03__sub">Every band below contains only palette colours. The intermediate tones are patterns, not blends.</p>
</header>
<div class="rt-03__swatches">
<span class="rt-03__sw rt-03__sw--0">#0f380f</span>
<span class="rt-03__sw rt-03__sw--1">#306230</span>
<span class="rt-03__sw rt-03__sw--2">#8bac0f</span>
<span class="rt-03__sw rt-03__sw--3">#9bbc0f</span>
</div>
<div class="rt-03__ramps">
<article class="rt-03__ramp" aria-labelledby="rt-03-r0">
<h4 class="rt-03__ramp-h" id="rt-03-r0">Reference · unlimited colour</h4>
<div class="rt-03__bar rt-03__bar--smooth" aria-hidden="true"></div>
<p class="rt-03__cap">A plain linear-gradient. Contains thousands of colours the hardware never had.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r1">
<h4 class="rt-03__ramp-h" id="rt-03-r1">2 colours · 2×2 Bayer</h4>
<div class="rt-03__bar" aria-hidden="true">
<span class="rt-03__band rt-03__band--a0"></span>
<span class="rt-03__band rt-03__band--a25"></span>
<span class="rt-03__band rt-03__band--a50"></span>
<span class="rt-03__band rt-03__band--a75"></span>
<span class="rt-03__band rt-03__band--a100"></span>
</div>
<p class="rt-03__cap">Darkest and lightest tone only. Five apparent steps from two real ones.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r2">
<h4 class="rt-03__ramp-h" id="rt-03-r2">4 colours · adjacent-pair dither</h4>
<div class="rt-03__bar" aria-hidden="true">
<span class="rt-03__band rt-03__band--b0"></span>
<span class="rt-03__band rt-03__band--b1"></span>
<span class="rt-03__band rt-03__band--b2"></span>
<span class="rt-03__band rt-03__band--b3"></span>
<span class="rt-03__band rt-03__band--b4"></span>
<span class="rt-03__band rt-03__band--b5"></span>
<span class="rt-03__band rt-03__band--b6"></span>
</div>
<p class="rt-03__cap">Each pattern mixes only its two neighbouring palette entries.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r3">
<h4 class="rt-03__ramp-h" id="rt-03-r3">8 steps · 4px cell, fine mask</h4>
<div class="rt-03__bar rt-03__bar--fine" aria-hidden="true">
<span class="rt-03__band rt-03__band--b0"></span>
<span class="rt-03__band rt-03__band--b1"></span>
<span class="rt-03__band rt-03__band--b2"></span>
<span class="rt-03__band rt-03__band--b3"></span>
<span class="rt-03__band rt-03__band--b4"></span>
<span class="rt-03__band rt-03__band--b5"></span>
<span class="rt-03__band rt-03__band--b6"></span>
<span class="rt-03__band rt-03__band--a100"></span>
</div>
<p class="rt-03__cap">Same construction at a 2px cell. Closer to a blend, further from a period-correct look.</p>
</article>
</div>
<aside class="rt-03__panel">
<h4 class="rt-03__panel-h">Coverage table</h4>
<dl class="rt-03__dl">
<div><dt>0%</dt><dd>flat base colour, no overlay</dd></div>
<div><dt>25%</dt><dd>one quadrant of the 2×2 cell</dd></div>
<div><dt>50%</dt><dd>two opposite quadrants — checkerboard</dd></div>
<div><dt>75%</dt><dd>three quadrants, inverse of 25%</dd></div>
<div><dt>100%</dt><dd>flat next palette colour</dd></div>
</dl>
</aside>
</div>
</section>
```
## CSS
```css
.rt-03 {
--rt-03-cell: 4px;
--rt-03-p0: #0f380f;
--rt-03-p1: #306230;
--rt-03-p2: #8bac0f;
--rt-03-p3: #9bbc0f;
--rt-03-cond: ui-sans-serif, "Helvetica Neue", "Arial Narrow", Helvetica, Arial, sans-serif;
--rt-03-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--rt-03-bg: #f2efe6;
--rt-03-bg: oklch(95% 0.012 100);
--rt-03-surface: #ffffff;
--rt-03-surface: oklch(100% 0 0);
--rt-03-ink: #15170f;
--rt-03-ink: oklch(19% 0.012 120);
--rt-03-ink-2: #4e5344;
--rt-03-ink-2: oklch(45% 0.017 120);
--rt-03-line: #b6b9a6;
--rt-03-line: oklch(77% 0.02 118);
--rt-03-acc: #2f6b12;
--rt-03-acc: oklch(46% 0.14 133);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--rt-03-bg);
color: var(--rt-03-ink);
font-family: var(--rt-03-cond);
-webkit-text-size-adjust: 100%;
}
.rt-03 *,
.rt-03 *::before,
.rt-03 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.rt-03 {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
}
.rt-03[data-theme="dark"],
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
@media (prefers-color-scheme: light) {
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
}
@media (prefers-color-scheme: dark) {
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #f2efe6;
--rt-03-bg: oklch(95% 0.012 100);
--rt-03-surface: #ffffff;
--rt-03-surface: oklch(100% 0 0);
--rt-03-ink: #15170f;
--rt-03-ink: oklch(19% 0.012 120);
--rt-03-ink-2: #4e5344;
--rt-03-ink-2: oklch(45% 0.017 120);
--rt-03-line: #b6b9a6;
--rt-03-line: oklch(77% 0.02 118);
--rt-03-acc: #2f6b12;
--rt-03-acc: oklch(46% 0.14 133);
}
}
.rt-03__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.rt-03__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 6;
}
.rt-03__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.rt-03__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--rt-03-line);
border-radius: 50%;
background: var(--rt-03-surface);
color: var(--rt-03-ink-2);
cursor: pointer;
}
.rt-03__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.rt-03__theme-input:focus-visible + .rt-03__theme-label {
outline: 2px solid var(--rt-03-acc);
outline-offset: 2px;
}
.rt-03__stage {
max-inline-size: 68rem;
margin-inline: auto;
padding: clamp(3.5rem, 3rem + 3vw, 5rem) clamp(0.85rem, 0.5rem + 2vw, 2rem) 4rem;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1.5rem, 1rem + 2vw, 2.25rem);
}
.rt-03__head {
display: grid;
gap: 0.45rem;
min-inline-size: 0;
}
.rt-03__kicker {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.68rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
}
.rt-03__h {
margin: 0;
font-size: clamp(1.35rem, 1rem + 2vw, 2.2rem);
line-height: 1.08;
letter-spacing: -0.03em;
font-weight: 700;
text-wrap: balance;
min-inline-size: 0;
}
.rt-03__sub {
margin: 0;
font-size: 0.95rem;
line-height: 1.55;
color: var(--rt-03-ink-2);
max-inline-size: 52ch;
text-wrap: pretty;
min-inline-size: 0;
}
.rt-03__swatches {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.rt-03__sw {
min-inline-size: 0;
flex: none;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
padding: 0.55rem 0.7rem;
border: 1px solid var(--rt-03-line);
letter-spacing: 0.03em;
}
.rt-03__sw--0 {
background: #0f380f;
color: #9bbc0f;
}
.rt-03__sw--1 {
background: #306230;
color: #dff0a8;
}
.rt-03__sw--2 {
background: #8bac0f;
color: #10290a;
}
.rt-03__sw--3 {
background: #9bbc0f;
color: #10290a;
}
.rt-03__ramps {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1.1rem;
}
.rt-03__ramp {
min-inline-size: 0;
display: grid;
gap: 0.6rem;
align-content: start;
background: var(--rt-03-surface);
border: 1px solid var(--rt-03-line);
padding: clamp(0.8rem, 0.6rem + 0.8vw, 1.1rem);
}
.rt-03__ramp-h {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
min-inline-size: 0;
}
.rt-03__bar {
display: flex;
block-size: clamp(64px, 8vw, 92px);
min-inline-size: 0;
border: 2px solid #0f380f;
overflow: hidden;
}
.rt-03__bar--smooth {
background: linear-gradient(90deg, #0f380f, #306230, #8bac0f, #9bbc0f);
}
.rt-03__band {
flex: 1 1 0;
min-inline-size: 0;
background-size: var(--rt-03-cell) var(--rt-03-cell);
}
.rt-03__bar--fine {
--rt-03-cell: 2px;
}
.rt-03__band--a0 {
background-color: var(--rt-03-p0);
}
.rt-03__band--a100 {
background-color: var(--rt-03-p3);
}
.rt-03__band--a25 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--a50 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p3) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--a75 {
background-color: var(--rt-03-p3);
background-image: repeating-conic-gradient(var(--rt-03-p0) 0% 25%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b0 {
background-color: var(--rt-03-p0);
}
.rt-03__band--b1 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p1) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p1) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b2 {
background-color: var(--rt-03-p1);
}
.rt-03__band--b3 {
background-color: var(--rt-03-p1);
background-image: repeating-conic-gradient(var(--rt-03-p2) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p2) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b4 {
background-color: var(--rt-03-p2);
}
.rt-03__band--b5 {
background-color: var(--rt-03-p2);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p3) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b6 {
background-color: var(--rt-03-p3);
}
@supports not (background: repeating-conic-gradient(#000 0% 25%, #fff 0% 50%)) {
.rt-03__band--a25,
.rt-03__band--a50,
.rt-03__band--a75,
.rt-03__band--b1,
.rt-03__band--b3,
.rt-03__band--b5 {
background-image: repeating-linear-gradient(45deg, currentColor 0 2px, rgba(0,0,0,0) 2px 4px);
}
.rt-03__band--a25,
.rt-03__band--a50 {
color: var(--rt-03-p3);
}
.rt-03__band--a75 {
color: var(--rt-03-p0);
}
.rt-03__band--b1 {
color: var(--rt-03-p1);
}
.rt-03__band--b3 {
color: var(--rt-03-p2);
}
.rt-03__band--b5 {
color: var(--rt-03-p3);
}
}
.rt-03__cap {
margin: 0;
font-size: 0.82rem;
line-height: 1.5;
color: var(--rt-03-ink-2);
min-inline-size: 0;
text-wrap: pretty;
}
.rt-03__panel {
min-inline-size: 0;
background: var(--rt-03-surface);
border: 1px solid var(--rt-03-line);
border-left: 4px solid var(--rt-03-acc);
padding: clamp(0.85rem, 0.6rem + 1vw, 1.2rem);
display: grid;
gap: 0.65rem;
}
.rt-03__panel-h {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
}
.rt-03__dl {
margin: 0;
display: grid;
gap: 0.3rem;
font-size: 0.85rem;
}
.rt-03__dl > div {
display: flex;
gap: 0.75rem;
align-items: baseline;
min-inline-size: 0;
}
.rt-03__dl dt {
margin: 0;
font-family: var(--rt-03-mono);
font-variant-numeric: tabular-nums;
inline-size: 3.5rem;
flex: none;
color: var(--rt-03-acc);
}
.rt-03__dl dd {
margin: 0;
color: var(--rt-03-ink-2);
min-inline-size: 0;
}
@media (min-width: 54rem) {
.rt-03__ramps {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
```Here's a working CSS Retro UI 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: Dithering Patterns with CSS Gradients
Source: https://codefronts.com/design-styles/css-retro-designs/dithering-patterns-with-css-gradients/
Ordered dithering is how a limited palette fakes colours it does not have: alternate two available colours in a fixed pattern and the eye averages them. Built here from layered repeating-conic-gradient checkerboards over flat fills, with a smooth gradient beside Bayer ramps at 2, 4 and 8 colours — and an honest statement of where the CSS approximation stops.
## HTML
```html
<section class="rt-03" aria-labelledby="rt-03-title">
<h2 class="rt-03__sr" id="rt-03-title">Ordered dithering ramps built from CSS gradients</h2>
<div class="rt-03__theme">
<input class="rt-03__theme-input" type="checkbox" id="rt-03-theme">
<label class="rt-03__theme-label" for="rt-03-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
<span class="rt-03__sr">Toggle dark surround</span>
</label>
</div>
<div class="rt-03__stage">
<header class="rt-03__head">
<p class="rt-03__kicker">Palette · Game Boy DMG-01 · 4 tones, exact sRGB</p>
<h3 class="rt-03__h">Four colours, twelve apparent tones</h3>
<p class="rt-03__sub">Every band below contains only palette colours. The intermediate tones are patterns, not blends.</p>
</header>
<div class="rt-03__swatches">
<span class="rt-03__sw rt-03__sw--0">#0f380f</span>
<span class="rt-03__sw rt-03__sw--1">#306230</span>
<span class="rt-03__sw rt-03__sw--2">#8bac0f</span>
<span class="rt-03__sw rt-03__sw--3">#9bbc0f</span>
</div>
<div class="rt-03__ramps">
<article class="rt-03__ramp" aria-labelledby="rt-03-r0">
<h4 class="rt-03__ramp-h" id="rt-03-r0">Reference · unlimited colour</h4>
<div class="rt-03__bar rt-03__bar--smooth" aria-hidden="true"></div>
<p class="rt-03__cap">A plain linear-gradient. Contains thousands of colours the hardware never had.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r1">
<h4 class="rt-03__ramp-h" id="rt-03-r1">2 colours · 2×2 Bayer</h4>
<div class="rt-03__bar" aria-hidden="true">
<span class="rt-03__band rt-03__band--a0"></span>
<span class="rt-03__band rt-03__band--a25"></span>
<span class="rt-03__band rt-03__band--a50"></span>
<span class="rt-03__band rt-03__band--a75"></span>
<span class="rt-03__band rt-03__band--a100"></span>
</div>
<p class="rt-03__cap">Darkest and lightest tone only. Five apparent steps from two real ones.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r2">
<h4 class="rt-03__ramp-h" id="rt-03-r2">4 colours · adjacent-pair dither</h4>
<div class="rt-03__bar" aria-hidden="true">
<span class="rt-03__band rt-03__band--b0"></span>
<span class="rt-03__band rt-03__band--b1"></span>
<span class="rt-03__band rt-03__band--b2"></span>
<span class="rt-03__band rt-03__band--b3"></span>
<span class="rt-03__band rt-03__band--b4"></span>
<span class="rt-03__band rt-03__band--b5"></span>
<span class="rt-03__band rt-03__band--b6"></span>
</div>
<p class="rt-03__cap">Each pattern mixes only its two neighbouring palette entries.</p>
</article>
<article class="rt-03__ramp" aria-labelledby="rt-03-r3">
<h4 class="rt-03__ramp-h" id="rt-03-r3">8 steps · 4px cell, fine mask</h4>
<div class="rt-03__bar rt-03__bar--fine" aria-hidden="true">
<span class="rt-03__band rt-03__band--b0"></span>
<span class="rt-03__band rt-03__band--b1"></span>
<span class="rt-03__band rt-03__band--b2"></span>
<span class="rt-03__band rt-03__band--b3"></span>
<span class="rt-03__band rt-03__band--b4"></span>
<span class="rt-03__band rt-03__band--b5"></span>
<span class="rt-03__band rt-03__band--b6"></span>
<span class="rt-03__band rt-03__band--a100"></span>
</div>
<p class="rt-03__cap">Same construction at a 2px cell. Closer to a blend, further from a period-correct look.</p>
</article>
</div>
<aside class="rt-03__panel">
<h4 class="rt-03__panel-h">Coverage table</h4>
<dl class="rt-03__dl">
<div><dt>0%</dt><dd>flat base colour, no overlay</dd></div>
<div><dt>25%</dt><dd>one quadrant of the 2×2 cell</dd></div>
<div><dt>50%</dt><dd>two opposite quadrants — checkerboard</dd></div>
<div><dt>75%</dt><dd>three quadrants, inverse of 25%</dd></div>
<div><dt>100%</dt><dd>flat next palette colour</dd></div>
</dl>
</aside>
</div>
</section>
```
## CSS
```css
.rt-03 {
--rt-03-cell: 4px;
--rt-03-p0: #0f380f;
--rt-03-p1: #306230;
--rt-03-p2: #8bac0f;
--rt-03-p3: #9bbc0f;
--rt-03-cond: ui-sans-serif, "Helvetica Neue", "Arial Narrow", Helvetica, Arial, sans-serif;
--rt-03-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--rt-03-bg: #f2efe6;
--rt-03-bg: oklch(95% 0.012 100);
--rt-03-surface: #ffffff;
--rt-03-surface: oklch(100% 0 0);
--rt-03-ink: #15170f;
--rt-03-ink: oklch(19% 0.012 120);
--rt-03-ink-2: #4e5344;
--rt-03-ink-2: oklch(45% 0.017 120);
--rt-03-line: #b6b9a6;
--rt-03-line: oklch(77% 0.02 118);
--rt-03-acc: #2f6b12;
--rt-03-acc: oklch(46% 0.14 133);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--rt-03-bg);
color: var(--rt-03-ink);
font-family: var(--rt-03-cond);
-webkit-text-size-adjust: 100%;
}
.rt-03 *,
.rt-03 *::before,
.rt-03 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.rt-03 {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
}
.rt-03[data-theme="dark"],
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
@media (prefers-color-scheme: light) {
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #14160f;
--rt-03-bg: oklch(19% 0.009 120);
--rt-03-surface: #1e2117;
--rt-03-surface: oklch(24% 0.012 120);
--rt-03-ink: #f0f2e7;
--rt-03-ink: oklch(96% 0.008 120);
--rt-03-ink-2: #a8ad99;
--rt-03-ink-2: oklch(74% 0.016 120);
--rt-03-line: #414635;
--rt-03-line: oklch(37% 0.018 120);
--rt-03-acc: #a4d96a;
--rt-03-acc: oklch(83% 0.15 133);
}
}
@media (prefers-color-scheme: dark) {
.rt-03:has(.rt-03__theme-input:checked) {
--rt-03-bg: #f2efe6;
--rt-03-bg: oklch(95% 0.012 100);
--rt-03-surface: #ffffff;
--rt-03-surface: oklch(100% 0 0);
--rt-03-ink: #15170f;
--rt-03-ink: oklch(19% 0.012 120);
--rt-03-ink-2: #4e5344;
--rt-03-ink-2: oklch(45% 0.017 120);
--rt-03-line: #b6b9a6;
--rt-03-line: oklch(77% 0.02 118);
--rt-03-acc: #2f6b12;
--rt-03-acc: oklch(46% 0.14 133);
}
}
.rt-03__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.rt-03__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 6;
}
.rt-03__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.rt-03__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--rt-03-line);
border-radius: 50%;
background: var(--rt-03-surface);
color: var(--rt-03-ink-2);
cursor: pointer;
}
.rt-03__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.rt-03__theme-input:focus-visible + .rt-03__theme-label {
outline: 2px solid var(--rt-03-acc);
outline-offset: 2px;
}
.rt-03__stage {
max-inline-size: 68rem;
margin-inline: auto;
padding: clamp(3.5rem, 3rem + 3vw, 5rem) clamp(0.85rem, 0.5rem + 2vw, 2rem) 4rem;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(1.5rem, 1rem + 2vw, 2.25rem);
}
.rt-03__head {
display: grid;
gap: 0.45rem;
min-inline-size: 0;
}
.rt-03__kicker {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.68rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
}
.rt-03__h {
margin: 0;
font-size: clamp(1.35rem, 1rem + 2vw, 2.2rem);
line-height: 1.08;
letter-spacing: -0.03em;
font-weight: 700;
text-wrap: balance;
min-inline-size: 0;
}
.rt-03__sub {
margin: 0;
font-size: 0.95rem;
line-height: 1.55;
color: var(--rt-03-ink-2);
max-inline-size: 52ch;
text-wrap: pretty;
min-inline-size: 0;
}
.rt-03__swatches {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.rt-03__sw {
min-inline-size: 0;
flex: none;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
padding: 0.55rem 0.7rem;
border: 1px solid var(--rt-03-line);
letter-spacing: 0.03em;
}
.rt-03__sw--0 {
background: #0f380f;
color: #9bbc0f;
}
.rt-03__sw--1 {
background: #306230;
color: #dff0a8;
}
.rt-03__sw--2 {
background: #8bac0f;
color: #10290a;
}
.rt-03__sw--3 {
background: #9bbc0f;
color: #10290a;
}
.rt-03__ramps {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1.1rem;
}
.rt-03__ramp {
min-inline-size: 0;
display: grid;
gap: 0.6rem;
align-content: start;
background: var(--rt-03-surface);
border: 1px solid var(--rt-03-line);
padding: clamp(0.8rem, 0.6rem + 0.8vw, 1.1rem);
}
.rt-03__ramp-h {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
min-inline-size: 0;
}
.rt-03__bar {
display: flex;
block-size: clamp(64px, 8vw, 92px);
min-inline-size: 0;
border: 2px solid #0f380f;
overflow: hidden;
}
.rt-03__bar--smooth {
background: linear-gradient(90deg, #0f380f, #306230, #8bac0f, #9bbc0f);
}
.rt-03__band {
flex: 1 1 0;
min-inline-size: 0;
background-size: var(--rt-03-cell) var(--rt-03-cell);
}
.rt-03__bar--fine {
--rt-03-cell: 2px;
}
.rt-03__band--a0 {
background-color: var(--rt-03-p0);
}
.rt-03__band--a100 {
background-color: var(--rt-03-p3);
}
.rt-03__band--a25 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--a50 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p3) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--a75 {
background-color: var(--rt-03-p3);
background-image: repeating-conic-gradient(var(--rt-03-p0) 0% 25%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b0 {
background-color: var(--rt-03-p0);
}
.rt-03__band--b1 {
background-color: var(--rt-03-p0);
background-image: repeating-conic-gradient(var(--rt-03-p1) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p1) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b2 {
background-color: var(--rt-03-p1);
}
.rt-03__band--b3 {
background-color: var(--rt-03-p1);
background-image: repeating-conic-gradient(var(--rt-03-p2) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p2) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b4 {
background-color: var(--rt-03-p2);
}
.rt-03__band--b5 {
background-color: var(--rt-03-p2);
background-image: repeating-conic-gradient(var(--rt-03-p3) 0% 25%, rgba(0,0,0,0) 0% 50%, var(--rt-03-p3) 0% 75%, rgba(0,0,0,0) 0% 100%);
}
.rt-03__band--b6 {
background-color: var(--rt-03-p3);
}
@supports not (background: repeating-conic-gradient(#000 0% 25%, #fff 0% 50%)) {
.rt-03__band--a25,
.rt-03__band--a50,
.rt-03__band--a75,
.rt-03__band--b1,
.rt-03__band--b3,
.rt-03__band--b5 {
background-image: repeating-linear-gradient(45deg, currentColor 0 2px, rgba(0,0,0,0) 2px 4px);
}
.rt-03__band--a25,
.rt-03__band--a50 {
color: var(--rt-03-p3);
}
.rt-03__band--a75 {
color: var(--rt-03-p0);
}
.rt-03__band--b1 {
color: var(--rt-03-p1);
}
.rt-03__band--b3 {
color: var(--rt-03-p2);
}
.rt-03__band--b5 {
color: var(--rt-03-p3);
}
}
.rt-03__cap {
margin: 0;
font-size: 0.82rem;
line-height: 1.5;
color: var(--rt-03-ink-2);
min-inline-size: 0;
text-wrap: pretty;
}
.rt-03__panel {
min-inline-size: 0;
background: var(--rt-03-surface);
border: 1px solid var(--rt-03-line);
border-left: 4px solid var(--rt-03-acc);
padding: clamp(0.85rem, 0.6rem + 1vw, 1.2rem);
display: grid;
gap: 0.65rem;
}
.rt-03__panel-h {
margin: 0;
font-family: var(--rt-03-mono);
font-size: 0.7rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--rt-03-ink-2);
}
.rt-03__dl {
margin: 0;
display: grid;
gap: 0.3rem;
font-size: 0.85rem;
}
.rt-03__dl > div {
display: flex;
gap: 0.75rem;
align-items: baseline;
min-inline-size: 0;
}
.rt-03__dl dt {
margin: 0;
font-family: var(--rt-03-mono);
font-variant-numeric: tabular-nums;
inline-size: 3.5rem;
flex: none;
color: var(--rt-03-acc);
}
.rt-03__dl dd {
margin: 0;
color: var(--rt-03-ink-2);
min-inline-size: 0;
}
@media (min-width: 54rem) {
.rt-03__ramps {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
```How this works
Dithering trades spatial resolution for colour resolution. If your palette has four greys and you need a fifth, you cannot mix one — so you interleave the two nearest greys in a regular pattern and let the viewer's eye do the averaging at normal viewing distance. A 50% checkerboard of black and white reads as mid grey. That is the entire idea, and it is why 1-bit and 4-colour machines could still show a gradient.
Ordered dithering means the pattern is fixed, not computed. Classic Bayer dithering uses a threshold matrix — a repeating 2×2, 4×4 or 8×8 grid of cut-off values — so the same input tone always produces the same dot arrangement. That determinism is what makes it expressible in CSS at all: a fixed pattern is a repeating gradient. Error diffusion (Floyd–Steinberg and friends) instead pushes each pixel's rounding error into its neighbours, which is per-pixel state and has no CSS equivalent.
The construction is a coverage stack. Each band sets a flat background-color from the palette, then paints the neighbouring palette colour over it with a repeating-conic-gradient that covers one, two or three quadrants of a 4px cell — 25%, 50%, 75%. Because both colours are real palette tokens, the result never contains a colour the palette does not have, which is the whole constraint the technique exists to satisfy.
Be honest about the limit. This is a decorative approximation. Real dithering samples an actual source image and picks a threshold per pixel; here the pattern is uniform within each band, so the transitions between bands are visible as steps rather than dissolving. It also costs real paint time: a full-viewport dithered gradient is a large tiled paint that repeats on every scroll-triggered repaint, and at that size a pre-rendered PNG or a canvas pass is the correct tool. Use the CSS version for panels, cards and headers, not for backgrounds the size of a page.
Make it yours
- Set
--rt-03-cellto 2px for a fine mask that nearly dissolves, or 8px for a coarse dot that reads as deliberate texture. - Swap the palette by editing
--rt-03-p0through--rt-03-p3; the ramps are built from those four tokens only. - Rotate the pattern by adding
from 45degto the conic gradients for a diamond dither instead of a square one. - Change the ramp direction by reordering the band list — the coverage percentages are independent of order.
- Add a fifth density at 12.5% with an 8px cell covering one quadrant of a quarter, which is where an 8×8 Bayer matrix gets its extra steps.
- Apply a single dither band as an overlay on an image with
mix-blend-mode: multiplyto get a printed-halftone feel without touching the image itself.
Gotchas — read before shipping
- This is ordered dithering, not error diffusion. There is no per-pixel error term, so smooth photographic sources will band; do not expect Floyd–Steinberg results from a repeating gradient.
- Large dithered areas are expensive to paint. A full-page tiled conic pattern repaints on scroll and shows up immediately in a performance profile — keep it to panels and headers.
- A dither pattern at a 1px or 2px cell interacts with fractional device pixel ratios and produces moiré. Test at 1.25x and 1.5x, or keep the cell at 4px and above.
- Dithered text backgrounds wreck contrast measurement. The effective background is neither of the two colours, so measure the text pair against the darker one — this demo puts all body copy on a flat fill for exactly that reason.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by :has(), oklch(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
repeating-conic-gradient lands at Chrome 69 / Safari 12.1 / Firefox 83 and is gated here with @supports, falling back to a diagonal repeating-linear-gradient that reads as a coarser dither rather than breaking. The real floor is oklch() for the surround tokens and the :has() theme toggle at Firefox 121 — the period palette itself is plain sRGB hex and paints everywhere.