02 / 20
All 20 in 20 Tailwind Retro UI
- 01Tailwind CRT Scanlines and Screen Curvature
- 02Tailwind Pixel Art Scaling with Image Rendering
- 03Tailwind Dithering Patterns with Gradients
- 04Tailwind One Bit Bevel Borders with Box Shadow
- 05Tailwind Classic Mac System 7 Window Chrome
- 06Tailwind MS DOS Text Mode Interface Grid
- 07Tailwind Teletext and Ceefax Block Graphics
- 08Tailwind Amber and Green Phosphor Monitor
- 09Tailwind Windows 95 Desktop Interface
- 10Tailwind Retro Futuristic Terminal Interface
- 11Tailwind Eight Bit Pixel Art Buttons and HUD
- 12Tailwind Neo Brutalist Component Board
- 13Tailwind Skeuomorphic Cassette Player
- 14Tailwind Vaporwave Aesthetic Scene
- 15Tailwind Retro Arcade Game Interface
- 16Tailwind ASCII Art Terminal Interface
- 17Tailwind Nineties Web Guestbook Widget
- 18Tailwind Y2K Aqua Gloss Interface Kit
- 19Tailwind Vintage Storefront Layout
- 20Tailwind Retro Desktop Portfolio Template
Tailwind Pixel Art Scaling with Image Rendering
The same 12×12 sprite at 1×, 4× and 8× with default smoothing on the left and nearest-neighbour on the right, both sides labelled, plus the two things people get wrong: a non-integer scale factor reintroduces blur even with the property set, and the property does nothing to a shape drawn in CSS rather than sampled from a bitmap. The port has one genuinely awkward corner — a two-declaration progressive fallback has no utility form.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="twr-02 relative block w-full min-h-svh box-border bg-twr02-bg text-twr02-ink font-twr02-sans" aria-labelledby="twr-02-title">
<h2 class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap" id="twr-02-title">Pixel art scaling comparison</h2>
<div class="absolute top-4 right-4 z-6">
<input class="peer absolute inset-0 size-11 m-0 opacity-0 cursor-pointer" type="checkbox" id="twr-02-theme">
<label class="grid place-items-center size-11 rounded-full border border-twr02-line bg-twr02-surface text-twr02-ink2 cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-twr02-acc peer-focus-visible:outline-offset-2" for="twr-02-theme">
<svg class="size-5" 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="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Toggle dark surround</span>
</label>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] gap-[clamp(1.5rem,1rem+2vw,2.5rem)] max-w-[72rem] mx-auto px-[clamp(0.85rem,0.5rem+2vw,2rem)] pt-[clamp(3.5rem,3rem+3vw,5rem)] pb-16">
<header class="grid gap-1.5 min-w-0">
<p class="m-0 font-twr02-mono text-[0.7rem] tracking-[0.14em] uppercase text-twr02-ink2">Source bitmap · 12 × 12 px · 5-colour</p>
<h3 class="m-0 min-w-0 text-[clamp(1.25rem,1rem+1.6vw,2rem)] font-[650] leading-[1.12] tracking-[-0.02em] text-balance">Nearest-neighbour against the default sampler</h3>
</header>
<div class="grid grid-cols-[minmax(0,1fr)] gap-4 md:grid-cols-2">
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-a">
<h4 class="m-0 flex flex-wrap items-center gap-1.5 min-w-0 text-[0.78rem] font-medium text-twr02-ink2" id="twr-02-a"><span class="min-w-0 [overflow-wrap:anywhere] font-twr02-mono text-[0.68rem] px-1.5 py-0.5 rounded-sm border border-current text-twr02-warn">image-rendering: auto</span> smoothed</h4>
<ul class="list-none m-0 p-0 flex flex-wrap items-end gap-3">
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">1×</span><img class="block size-3" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at original size, smoothed"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">4×</span><img class="block w-[calc(12*var(--twr-02-px))] h-[calc(12*var(--twr-02-px))]" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at four times size, smoothed"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">8×</span><img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))]" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at eight times size, smoothed"></li>
</ul>
</article>
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-b">
<h4 class="m-0 flex flex-wrap items-center gap-1.5 min-w-0 text-[0.78rem] font-medium text-twr02-ink2" id="twr-02-b"><span class="min-w-0 [overflow-wrap:anywhere] font-twr02-mono text-[0.68rem] px-1.5 py-0.5 rounded-sm border border-current text-twr02-ok">image-rendering: pixelated</span> nearest-neighbour</h4>
<ul class="list-none m-0 p-0 flex flex-wrap items-end gap-3">
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">1×</span><img class="block size-3 twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at original size, nearest-neighbour"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">4×</span><img class="block w-[calc(12*var(--twr-02-px))] h-[calc(12*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at four times size, nearest-neighbour"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">8×</span><img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at eight times size, nearest-neighbour"></li>
</ul>
</article>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] gap-4 md:grid-cols-2">
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-t1">
<h4 class="m-0 font-twr02-mono text-[0.72rem] tracking-[0.1em] uppercase text-twr02-warn" id="twr-02-t1">Trap 1 · fractional scale</h4>
<div class="flex flex-wrap gap-4">
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite scaled by a whole number">
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">8.0× — integer, clean</figcaption>
</figure>
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<img class="block w-[calc(12px*var(--twr-02-bad)*1.4)] h-[calc(12px*var(--twr-02-bad)*1.4)] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite scaled by a fractional factor">
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">5.7× — fractional, resampled</figcaption>
</figure>
</div>
<p class="m-0 min-w-0 text-[0.78rem] leading-normal text-pretty text-twr02-ink2">Both carry <code class="font-twr02-mono text-[0.92em] bg-twr02-cell px-1 rounded-sm">[image-rendering:pixelated]</code>. The property cannot invent a fractional device pixel.</p>
</article>
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-t2">
<h4 class="m-0 font-twr02-mono text-[0.72rem] tracking-[0.1em] uppercase text-twr02-warn" id="twr-02-t2">Trap 2 · CSS-drawn shapes</h4>
<div class="flex flex-wrap gap-4">
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<span class="size-24 rounded-full bg-[#2ce8f5] border-4 border-[#0b0f14] [image-rendering:pixelated]" aria-hidden="true"></span>
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">rounded-full disc — property ignored</figcaption>
</figure>
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<span class="twr-02__stepped size-2 m-11 bg-transparent" aria-hidden="true"></span>
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">stepped box-shadow — real pixels</figcaption>
</figure>
</div>
<p class="m-0 min-w-0 text-[0.78rem] leading-normal text-pretty text-twr02-ink2"><code class="font-twr02-mono text-[0.92em] bg-twr02-cell px-1 rounded-sm">image-rendering</code> is a hint to the image sampler. A painted shape never reaches it.</p>
</article>
</div>
</div>
</section><section class="twr-02 relative block w-full min-h-svh box-border bg-twr02-bg text-twr02-ink font-twr02-sans" aria-labelledby="twr-02-title">
<h2 class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap" id="twr-02-title">Pixel art scaling comparison</h2>
<div class="absolute top-4 right-4 z-6">
<input class="peer absolute inset-0 size-11 m-0 opacity-0 cursor-pointer" type="checkbox" id="twr-02-theme">
<label class="grid place-items-center size-11 rounded-full border border-twr02-line bg-twr02-surface text-twr02-ink2 cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-twr02-acc peer-focus-visible:outline-offset-2" for="twr-02-theme">
<svg class="size-5" 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="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Toggle dark surround</span>
</label>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] gap-[clamp(1.5rem,1rem+2vw,2.5rem)] max-w-[72rem] mx-auto px-[clamp(0.85rem,0.5rem+2vw,2rem)] pt-[clamp(3.5rem,3rem+3vw,5rem)] pb-16">
<header class="grid gap-1.5 min-w-0">
<p class="m-0 font-twr02-mono text-[0.7rem] tracking-[0.14em] uppercase text-twr02-ink2">Source bitmap · 12 × 12 px · 5-colour</p>
<h3 class="m-0 min-w-0 text-[clamp(1.25rem,1rem+1.6vw,2rem)] font-[650] leading-[1.12] tracking-[-0.02em] text-balance">Nearest-neighbour against the default sampler</h3>
</header>
<div class="grid grid-cols-[minmax(0,1fr)] gap-4 md:grid-cols-2">
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-a">
<h4 class="m-0 flex flex-wrap items-center gap-1.5 min-w-0 text-[0.78rem] font-medium text-twr02-ink2" id="twr-02-a"><span class="min-w-0 [overflow-wrap:anywhere] font-twr02-mono text-[0.68rem] px-1.5 py-0.5 rounded-sm border border-current text-twr02-warn">image-rendering: auto</span> smoothed</h4>
<ul class="list-none m-0 p-0 flex flex-wrap items-end gap-3">
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">1×</span><img class="block size-3" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at original size, smoothed"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">4×</span><img class="block w-[calc(12*var(--twr-02-px))] h-[calc(12*var(--twr-02-px))]" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at four times size, smoothed"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">8×</span><img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))]" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at eight times size, smoothed"></li>
</ul>
</article>
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-b">
<h4 class="m-0 flex flex-wrap items-center gap-1.5 min-w-0 text-[0.78rem] font-medium text-twr02-ink2" id="twr-02-b"><span class="min-w-0 [overflow-wrap:anywhere] font-twr02-mono text-[0.68rem] px-1.5 py-0.5 rounded-sm border border-current text-twr02-ok">image-rendering: pixelated</span> nearest-neighbour</h4>
<ul class="list-none m-0 p-0 flex flex-wrap items-end gap-3">
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">1×</span><img class="block size-3 twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at original size, nearest-neighbour"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">4×</span><img class="block w-[calc(12*var(--twr-02-px))] h-[calc(12*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at four times size, nearest-neighbour"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">8×</span><img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at eight times size, nearest-neighbour"></li>
</ul>
</article>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] gap-4 md:grid-cols-2">
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-t1">
<h4 class="m-0 font-twr02-mono text-[0.72rem] tracking-[0.1em] uppercase text-twr02-warn" id="twr-02-t1">Trap 1 · fractional scale</h4>
<div class="flex flex-wrap gap-4">
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite scaled by a whole number">
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">8.0× — integer, clean</figcaption>
</figure>
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<img class="block w-[calc(12px*var(--twr-02-bad)*1.4)] h-[calc(12px*var(--twr-02-bad)*1.4)] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite scaled by a fractional factor">
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">5.7× — fractional, resampled</figcaption>
</figure>
</div>
<p class="m-0 min-w-0 text-[0.78rem] leading-normal text-pretty text-twr02-ink2">Both carry <code class="font-twr02-mono text-[0.92em] bg-twr02-cell px-1 rounded-sm">[image-rendering:pixelated]</code>. The property cannot invent a fractional device pixel.</p>
</article>
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-t2">
<h4 class="m-0 font-twr02-mono text-[0.72rem] tracking-[0.1em] uppercase text-twr02-warn" id="twr-02-t2">Trap 2 · CSS-drawn shapes</h4>
<div class="flex flex-wrap gap-4">
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<span class="size-24 rounded-full bg-[#2ce8f5] border-4 border-[#0b0f14] [image-rendering:pixelated]" aria-hidden="true"></span>
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">rounded-full disc — property ignored</figcaption>
</figure>
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<span class="twr-02__stepped size-2 m-11 bg-transparent" aria-hidden="true"></span>
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">stepped box-shadow — real pixels</figcaption>
</figure>
</div>
<p class="m-0 min-w-0 text-[0.78rem] leading-normal text-pretty text-twr02-ink2"><code class="font-twr02-mono text-[0.92em] bg-twr02-cell px-1 rounded-sm">image-rendering</code> is a hint to the image sampler. A painted shape never reaches it.</p>
</article>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-twr02-bg: #efece4;
--color-twr02-bg: oklch(0.94 0.011 90);
--color-twr02-surface: #fffdf8;
--color-twr02-surface: oklch(0.99 0.006 90);
--color-twr02-cell: #e4e0d5;
--color-twr02-cell: oklch(0.90 0.013 90);
--color-twr02-ink: #191712;
--color-twr02-ink: oklch(0.19 0.01 80);
--color-twr02-ink2: #555044;
--color-twr02-ink2: oklch(0.45 0.015 80);
--color-twr02-line: #b8b2a3;
--color-twr02-line: oklch(0.77 0.017 90);
--color-twr02-acc: #b4321e;
--color-twr02-acc: oklch(0.50 0.17 32);
--color-twr02-warn: #b4321e;
--color-twr02-warn: oklch(0.50 0.17 32);
--color-twr02-ok: #1c6b3a;
--color-twr02-ok: oklch(0.47 0.12 152);
--font-twr02-sans: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-twr02-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.twr-02 {
--twr-02-px: 4px;
--twr-02-bad: 5.7;
}
/* A progressive two-declaration fallback has no utility form: two competing
utilities land in the same layer and source order decides. Keep the pair. */
.twr-02__sharp {
image-rendering: crisp-edges;
image-rendering: pixelated;
}
/* Sixty shadow layers is not a design-scale value. As one arbitrary token it
would be an unreadable multi-thousand-character class. */
.twr-02__stepped {
box-shadow: -24px -40px 0 0 #0b0f14, -16px -40px 0 0 #0b0f14, -8px -40px 0 0 #0b0f14, 0 -40px 0 0 #0b0f14, 8px -40px 0 0 #0b0f14, -40px -32px 0 0 #0b0f14, -32px -32px 0 0 #2ce8f5, -24px -32px 0 0 #2ce8f5, -16px -32px 0 0 #2ce8f5, -8px -32px 0 0 #2ce8f5, 0 -32px 0 0 #2ce8f5, 8px -32px 0 0 #2ce8f5, 16px -32px 0 0 #0b0f14, -48px -24px 0 0 #0b0f14, -40px -24px 0 0 #2ce8f5, -32px -24px 0 0 #2ce8f5, -24px -24px 0 0 #2ce8f5, -16px -24px 0 0 #2ce8f5, -8px -24px 0 0 #2ce8f5, 0 -24px 0 0 #2ce8f5, 8px -24px 0 0 #2ce8f5, 16px -24px 0 0 #2ce8f5, 24px -24px 0 0 #0b0f14, -48px -16px 0 0 #0b0f14, -40px -16px 0 0 #2ce8f5, -32px -16px 0 0 #2ce8f5, -24px -16px 0 0 #2ce8f5, -16px -16px 0 0 #2ce8f5, -8px -16px 0 0 #2ce8f5, 0 -16px 0 0 #2ce8f5, 8px -16px 0 0 #2ce8f5, 16px -16px 0 0 #2ce8f5, 24px -16px 0 0 #0b0f14, -48px -8px 0 0 #0b0f14, -40px -8px 0 0 #2ce8f5, -32px -8px 0 0 #2ce8f5, -24px -8px 0 0 #2ce8f5, -16px -8px 0 0 #2ce8f5, -8px -8px 0 0 #2ce8f5, 0 -8px 0 0 #2ce8f5, 8px -8px 0 0 #2ce8f5, 16px -8px 0 0 #2ce8f5, 24px -8px 0 0 #0b0f14, -48px 0 0 0 #0b0f14, -40px 0 0 0 #2ce8f5, -32px 0 0 0 #2ce8f5, -24px 0 0 0 #2ce8f5, -16px 0 0 0 #2ce8f5, -8px 0 0 0 #2ce8f5, 0 0 0 0 #2ce8f5, 8px 0 0 0 #2ce8f5, 16px 0 0 0 #2ce8f5, 24px 0 0 0 #0b0f14, -40px 8px 0 0 #0b0f14, -32px 8px 0 0 #2ce8f5, -24px 8px 0 0 #2ce8f5, -16px 8px 0 0 #2ce8f5, -8px 8px 0 0 #2ce8f5, 0 8px 0 0 #2ce8f5, 8px 8px 0 0 #2ce8f5, 16px 8px 0 0 #0b0f14, -32px 16px 0 0 #0b0f14, -24px 16px 0 0 #0b0f14, -16px 16px 0 0 #0b0f14, -8px 16px 0 0 #0b0f14, 0 16px 0 0 #0b0f14, 8px 16px 0 0 #0b0f14;
}
.twr-02:has(#twr-02-theme:checked) {
--color-twr02-bg: #16150f;
--color-twr02-bg: oklch(0.19 0.008 90);
--color-twr02-surface: #201e17;
--color-twr02-surface: oklch(0.25 0.01 90);
--color-twr02-cell: #2b291f;
--color-twr02-cell: oklch(0.30 0.012 90);
--color-twr02-ink: #f3f0e8;
--color-twr02-ink: oklch(0.96 0.006 90);
--color-twr02-ink2: #aca597;
--color-twr02-ink2: oklch(0.74 0.012 90);
--color-twr02-line: #454134;
--color-twr02-line: oklch(0.38 0.014 90);
--color-twr02-acc: #ff8f74;
--color-twr02-acc: oklch(0.76 0.13 36);
--color-twr02-warn: #ff8f74;
--color-twr02-warn: oklch(0.76 0.13 36);
--color-twr02-ok: #6fd396;
--color-twr02-ok: oklch(0.78 0.13 152);
}@import "tailwindcss";
@theme {
--color-twr02-bg: #efece4;
--color-twr02-bg: oklch(0.94 0.011 90);
--color-twr02-surface: #fffdf8;
--color-twr02-surface: oklch(0.99 0.006 90);
--color-twr02-cell: #e4e0d5;
--color-twr02-cell: oklch(0.90 0.013 90);
--color-twr02-ink: #191712;
--color-twr02-ink: oklch(0.19 0.01 80);
--color-twr02-ink2: #555044;
--color-twr02-ink2: oklch(0.45 0.015 80);
--color-twr02-line: #b8b2a3;
--color-twr02-line: oklch(0.77 0.017 90);
--color-twr02-acc: #b4321e;
--color-twr02-acc: oklch(0.50 0.17 32);
--color-twr02-warn: #b4321e;
--color-twr02-warn: oklch(0.50 0.17 32);
--color-twr02-ok: #1c6b3a;
--color-twr02-ok: oklch(0.47 0.12 152);
--font-twr02-sans: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-twr02-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.twr-02 {
--twr-02-px: 4px;
--twr-02-bad: 5.7;
}
/* A progressive two-declaration fallback has no utility form: two competing
utilities land in the same layer and source order decides. Keep the pair. */
.twr-02__sharp {
image-rendering: crisp-edges;
image-rendering: pixelated;
}
/* Sixty shadow layers is not a design-scale value. As one arbitrary token it
would be an unreadable multi-thousand-character class. */
.twr-02__stepped {
box-shadow: -24px -40px 0 0 #0b0f14, -16px -40px 0 0 #0b0f14, -8px -40px 0 0 #0b0f14, 0 -40px 0 0 #0b0f14, 8px -40px 0 0 #0b0f14, -40px -32px 0 0 #0b0f14, -32px -32px 0 0 #2ce8f5, -24px -32px 0 0 #2ce8f5, -16px -32px 0 0 #2ce8f5, -8px -32px 0 0 #2ce8f5, 0 -32px 0 0 #2ce8f5, 8px -32px 0 0 #2ce8f5, 16px -32px 0 0 #0b0f14, -48px -24px 0 0 #0b0f14, -40px -24px 0 0 #2ce8f5, -32px -24px 0 0 #2ce8f5, -24px -24px 0 0 #2ce8f5, -16px -24px 0 0 #2ce8f5, -8px -24px 0 0 #2ce8f5, 0 -24px 0 0 #2ce8f5, 8px -24px 0 0 #2ce8f5, 16px -24px 0 0 #2ce8f5, 24px -24px 0 0 #0b0f14, -48px -16px 0 0 #0b0f14, -40px -16px 0 0 #2ce8f5, -32px -16px 0 0 #2ce8f5, -24px -16px 0 0 #2ce8f5, -16px -16px 0 0 #2ce8f5, -8px -16px 0 0 #2ce8f5, 0 -16px 0 0 #2ce8f5, 8px -16px 0 0 #2ce8f5, 16px -16px 0 0 #2ce8f5, 24px -16px 0 0 #0b0f14, -48px -8px 0 0 #0b0f14, -40px -8px 0 0 #2ce8f5, -32px -8px 0 0 #2ce8f5, -24px -8px 0 0 #2ce8f5, -16px -8px 0 0 #2ce8f5, -8px -8px 0 0 #2ce8f5, 0 -8px 0 0 #2ce8f5, 8px -8px 0 0 #2ce8f5, 16px -8px 0 0 #2ce8f5, 24px -8px 0 0 #0b0f14, -48px 0 0 0 #0b0f14, -40px 0 0 0 #2ce8f5, -32px 0 0 0 #2ce8f5, -24px 0 0 0 #2ce8f5, -16px 0 0 0 #2ce8f5, -8px 0 0 0 #2ce8f5, 0 0 0 0 #2ce8f5, 8px 0 0 0 #2ce8f5, 16px 0 0 0 #2ce8f5, 24px 0 0 0 #0b0f14, -40px 8px 0 0 #0b0f14, -32px 8px 0 0 #2ce8f5, -24px 8px 0 0 #2ce8f5, -16px 8px 0 0 #2ce8f5, -8px 8px 0 0 #2ce8f5, 0 8px 0 0 #2ce8f5, 8px 8px 0 0 #2ce8f5, 16px 8px 0 0 #0b0f14, -32px 16px 0 0 #0b0f14, -24px 16px 0 0 #0b0f14, -16px 16px 0 0 #0b0f14, -8px 16px 0 0 #0b0f14, 0 16px 0 0 #0b0f14, 8px 16px 0 0 #0b0f14;
}
.twr-02:has(#twr-02-theme:checked) {
--color-twr02-bg: #16150f;
--color-twr02-bg: oklch(0.19 0.008 90);
--color-twr02-surface: #201e17;
--color-twr02-surface: oklch(0.25 0.01 90);
--color-twr02-cell: #2b291f;
--color-twr02-cell: oklch(0.30 0.012 90);
--color-twr02-ink: #f3f0e8;
--color-twr02-ink: oklch(0.96 0.006 90);
--color-twr02-ink2: #aca597;
--color-twr02-ink2: oklch(0.74 0.012 90);
--color-twr02-line: #454134;
--color-twr02-line: oklch(0.38 0.014 90);
--color-twr02-acc: #ff8f74;
--color-twr02-acc: oklch(0.76 0.13 36);
--color-twr02-warn: #ff8f74;
--color-twr02-warn: oklch(0.76 0.13 36);
--color-twr02-ok: #6fd396;
--color-twr02-ok: oklch(0.78 0.13 152);
}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-twr02-bg: #efece4;
--color-twr02-bg: oklch(0.94 0.011 90);
--color-twr02-surface: #fffdf8;
--color-twr02-surface: oklch(0.99 0.006 90);
--color-twr02-cell: #e4e0d5;
--color-twr02-cell: oklch(0.90 0.013 90);
--color-twr02-ink: #191712;
--color-twr02-ink: oklch(0.19 0.01 80);
--color-twr02-ink2: #555044;
--color-twr02-ink2: oklch(0.45 0.015 80);
--color-twr02-line: #b8b2a3;
--color-twr02-line: oklch(0.77 0.017 90);
--color-twr02-acc: #b4321e;
--color-twr02-acc: oklch(0.50 0.17 32);
--color-twr02-warn: #b4321e;
--color-twr02-warn: oklch(0.50 0.17 32);
--color-twr02-ok: #1c6b3a;
--color-twr02-ok: oklch(0.47 0.12 152);
--font-twr02-sans: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-twr02-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.twr-02 {
--twr-02-px: 4px;
--twr-02-bad: 5.7;
}
/* A progressive two-declaration fallback has no utility form: two competing
utilities land in the same layer and source order decides. Keep the pair. */
.twr-02__sharp {
image-rendering: crisp-edges;
image-rendering: pixelated;
}
/* Sixty shadow layers is not a design-scale value. As one arbitrary token it
would be an unreadable multi-thousand-character class. */
.twr-02__stepped {
box-shadow: -24px -40px 0 0 #0b0f14, -16px -40px 0 0 #0b0f14, -8px -40px 0 0 #0b0f14, 0 -40px 0 0 #0b0f14, 8px -40px 0 0 #0b0f14, -40px -32px 0 0 #0b0f14, -32px -32px 0 0 #2ce8f5, -24px -32px 0 0 #2ce8f5, -16px -32px 0 0 #2ce8f5, -8px -32px 0 0 #2ce8f5, 0 -32px 0 0 #2ce8f5, 8px -32px 0 0 #2ce8f5, 16px -32px 0 0 #0b0f14, -48px -24px 0 0 #0b0f14, -40px -24px 0 0 #2ce8f5, -32px -24px 0 0 #2ce8f5, -24px -24px 0 0 #2ce8f5, -16px -24px 0 0 #2ce8f5, -8px -24px 0 0 #2ce8f5, 0 -24px 0 0 #2ce8f5, 8px -24px 0 0 #2ce8f5, 16px -24px 0 0 #2ce8f5, 24px -24px 0 0 #0b0f14, -48px -16px 0 0 #0b0f14, -40px -16px 0 0 #2ce8f5, -32px -16px 0 0 #2ce8f5, -24px -16px 0 0 #2ce8f5, -16px -16px 0 0 #2ce8f5, -8px -16px 0 0 #2ce8f5, 0 -16px 0 0 #2ce8f5, 8px -16px 0 0 #2ce8f5, 16px -16px 0 0 #2ce8f5, 24px -16px 0 0 #0b0f14, -48px -8px 0 0 #0b0f14, -40px -8px 0 0 #2ce8f5, -32px -8px 0 0 #2ce8f5, -24px -8px 0 0 #2ce8f5, -16px -8px 0 0 #2ce8f5, -8px -8px 0 0 #2ce8f5, 0 -8px 0 0 #2ce8f5, 8px -8px 0 0 #2ce8f5, 16px -8px 0 0 #2ce8f5, 24px -8px 0 0 #0b0f14, -48px 0 0 0 #0b0f14, -40px 0 0 0 #2ce8f5, -32px 0 0 0 #2ce8f5, -24px 0 0 0 #2ce8f5, -16px 0 0 0 #2ce8f5, -8px 0 0 0 #2ce8f5, 0 0 0 0 #2ce8f5, 8px 0 0 0 #2ce8f5, 16px 0 0 0 #2ce8f5, 24px 0 0 0 #0b0f14, -40px 8px 0 0 #0b0f14, -32px 8px 0 0 #2ce8f5, -24px 8px 0 0 #2ce8f5, -16px 8px 0 0 #2ce8f5, -8px 8px 0 0 #2ce8f5, 0 8px 0 0 #2ce8f5, 8px 8px 0 0 #2ce8f5, 16px 8px 0 0 #0b0f14, -32px 16px 0 0 #0b0f14, -24px 16px 0 0 #0b0f14, -16px 16px 0 0 #0b0f14, -8px 16px 0 0 #0b0f14, 0 16px 0 0 #0b0f14, 8px 16px 0 0 #0b0f14;
}
.twr-02:has(#twr-02-theme:checked) {
--color-twr02-bg: #16150f;
--color-twr02-bg: oklch(0.19 0.008 90);
--color-twr02-surface: #201e17;
--color-twr02-surface: oklch(0.25 0.01 90);
--color-twr02-cell: #2b291f;
--color-twr02-cell: oklch(0.30 0.012 90);
--color-twr02-ink: #f3f0e8;
--color-twr02-ink: oklch(0.96 0.006 90);
--color-twr02-ink2: #aca597;
--color-twr02-ink2: oklch(0.74 0.012 90);
--color-twr02-line: #454134;
--color-twr02-line: oklch(0.38 0.014 90);
--color-twr02-acc: #ff8f74;
--color-twr02-acc: oklch(0.76 0.13 36);
--color-twr02-warn: #ff8f74;
--color-twr02-warn: oklch(0.76 0.13 36);
--color-twr02-ok: #6fd396;
--color-twr02-ok: oklch(0.78 0.13 152);
}@import "tailwindcss";
@theme {
--color-twr02-bg: #efece4;
--color-twr02-bg: oklch(0.94 0.011 90);
--color-twr02-surface: #fffdf8;
--color-twr02-surface: oklch(0.99 0.006 90);
--color-twr02-cell: #e4e0d5;
--color-twr02-cell: oklch(0.90 0.013 90);
--color-twr02-ink: #191712;
--color-twr02-ink: oklch(0.19 0.01 80);
--color-twr02-ink2: #555044;
--color-twr02-ink2: oklch(0.45 0.015 80);
--color-twr02-line: #b8b2a3;
--color-twr02-line: oklch(0.77 0.017 90);
--color-twr02-acc: #b4321e;
--color-twr02-acc: oklch(0.50 0.17 32);
--color-twr02-warn: #b4321e;
--color-twr02-warn: oklch(0.50 0.17 32);
--color-twr02-ok: #1c6b3a;
--color-twr02-ok: oklch(0.47 0.12 152);
--font-twr02-sans: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-twr02-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.twr-02 {
--twr-02-px: 4px;
--twr-02-bad: 5.7;
}
/* A progressive two-declaration fallback has no utility form: two competing
utilities land in the same layer and source order decides. Keep the pair. */
.twr-02__sharp {
image-rendering: crisp-edges;
image-rendering: pixelated;
}
/* Sixty shadow layers is not a design-scale value. As one arbitrary token it
would be an unreadable multi-thousand-character class. */
.twr-02__stepped {
box-shadow: -24px -40px 0 0 #0b0f14, -16px -40px 0 0 #0b0f14, -8px -40px 0 0 #0b0f14, 0 -40px 0 0 #0b0f14, 8px -40px 0 0 #0b0f14, -40px -32px 0 0 #0b0f14, -32px -32px 0 0 #2ce8f5, -24px -32px 0 0 #2ce8f5, -16px -32px 0 0 #2ce8f5, -8px -32px 0 0 #2ce8f5, 0 -32px 0 0 #2ce8f5, 8px -32px 0 0 #2ce8f5, 16px -32px 0 0 #0b0f14, -48px -24px 0 0 #0b0f14, -40px -24px 0 0 #2ce8f5, -32px -24px 0 0 #2ce8f5, -24px -24px 0 0 #2ce8f5, -16px -24px 0 0 #2ce8f5, -8px -24px 0 0 #2ce8f5, 0 -24px 0 0 #2ce8f5, 8px -24px 0 0 #2ce8f5, 16px -24px 0 0 #2ce8f5, 24px -24px 0 0 #0b0f14, -48px -16px 0 0 #0b0f14, -40px -16px 0 0 #2ce8f5, -32px -16px 0 0 #2ce8f5, -24px -16px 0 0 #2ce8f5, -16px -16px 0 0 #2ce8f5, -8px -16px 0 0 #2ce8f5, 0 -16px 0 0 #2ce8f5, 8px -16px 0 0 #2ce8f5, 16px -16px 0 0 #2ce8f5, 24px -16px 0 0 #0b0f14, -48px -8px 0 0 #0b0f14, -40px -8px 0 0 #2ce8f5, -32px -8px 0 0 #2ce8f5, -24px -8px 0 0 #2ce8f5, -16px -8px 0 0 #2ce8f5, -8px -8px 0 0 #2ce8f5, 0 -8px 0 0 #2ce8f5, 8px -8px 0 0 #2ce8f5, 16px -8px 0 0 #2ce8f5, 24px -8px 0 0 #0b0f14, -48px 0 0 0 #0b0f14, -40px 0 0 0 #2ce8f5, -32px 0 0 0 #2ce8f5, -24px 0 0 0 #2ce8f5, -16px 0 0 0 #2ce8f5, -8px 0 0 0 #2ce8f5, 0 0 0 0 #2ce8f5, 8px 0 0 0 #2ce8f5, 16px 0 0 0 #2ce8f5, 24px 0 0 0 #0b0f14, -40px 8px 0 0 #0b0f14, -32px 8px 0 0 #2ce8f5, -24px 8px 0 0 #2ce8f5, -16px 8px 0 0 #2ce8f5, -8px 8px 0 0 #2ce8f5, 0 8px 0 0 #2ce8f5, 8px 8px 0 0 #2ce8f5, 16px 8px 0 0 #0b0f14, -32px 16px 0 0 #0b0f14, -24px 16px 0 0 #0b0f14, -16px 16px 0 0 #0b0f14, -8px 16px 0 0 #0b0f14, 0 16px 0 0 #0b0f14, 8px 16px 0 0 #0b0f14;
}
.twr-02:has(#twr-02-theme:checked) {
--color-twr02-bg: #16150f;
--color-twr02-bg: oklch(0.19 0.008 90);
--color-twr02-surface: #201e17;
--color-twr02-surface: oklch(0.25 0.01 90);
--color-twr02-cell: #2b291f;
--color-twr02-cell: oklch(0.30 0.012 90);
--color-twr02-ink: #f3f0e8;
--color-twr02-ink: oklch(0.96 0.006 90);
--color-twr02-ink2: #aca597;
--color-twr02-ink2: oklch(0.74 0.012 90);
--color-twr02-line: #454134;
--color-twr02-line: oklch(0.38 0.014 90);
--color-twr02-acc: #ff8f74;
--color-twr02-acc: oklch(0.76 0.13 36);
--color-twr02-warn: #ff8f74;
--color-twr02-warn: oklch(0.76 0.13 36);
--color-twr02-ok: #6fd396;
--color-twr02-ok: oklch(0.78 0.13 152);
}<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=Archivo:wght@400;600;650&family=JetBrains+Mono:wght@400;500&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=Archivo:wght@400;600;650&family=JetBrains+Mono:wght@400;500&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The webfonts load as
<link>tags insetup, not as an@importin the stylesheet. Tailwind emits user@importrules after its own@layerblocks, and a late@importis invalid CSS — the font silently never loads. [image-rendering:pixelated]is the whole mechanism as a utility, and it reads better in the markup than in a rule: you can see which specimens are sharpened without opening a stylesheet.- The progressive
crisp-edgesthenpixelatedfallback stays a class. This is the general rule for any two-declaration fallback — utilities cannot encode cascade order between competing values of one property. - Integer scaling survives the port intact:
w-[calc(12*var(--twr-02-px))]keeps one variable in charge of every specimen size. - The checkerboard ground is one arbitrary background plus size and a two-value position, all underscore-escaped — a good example of how far arbitrary values stretch before they stop being readable.
- The sixty-layer stepped glyph is where they stop being readable, so it stays in CSS. Length, not capability, is the deciding factor.
md:grid-cols-2replaces the two@media (min-width: 52rem)blocks; the breakpoint is now visible on the element it affects.
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 Pixel Art Scaling with Image Rendering
Source: https://codefronts.com/design-styles/tailwind-retro-ui/tailwind-pixel-art-scaling-with-image-rendering/
The same 12×12 sprite at 1×, 4× and 8× with default smoothing on the left and nearest-neighbour on the right, both sides labelled, plus the two things people get wrong: a non-integer scale factor reintroduces blur even with the property set, and the property does nothing to a shape drawn in CSS rather than sampled from a bitmap. The port has one genuinely awkward corner — a two-declaration progressive fallback has no utility form.
## HTML
```html
<section class="twr-02 relative block w-full min-h-svh box-border bg-twr02-bg text-twr02-ink font-twr02-sans" aria-labelledby="twr-02-title">
<h2 class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap" id="twr-02-title">Pixel art scaling comparison</h2>
<div class="absolute top-4 right-4 z-6">
<input class="peer absolute inset-0 size-11 m-0 opacity-0 cursor-pointer" type="checkbox" id="twr-02-theme">
<label class="grid place-items-center size-11 rounded-full border border-twr02-line bg-twr02-surface text-twr02-ink2 cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-twr02-acc peer-focus-visible:outline-offset-2" for="twr-02-theme">
<svg class="size-5" 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="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Toggle dark surround</span>
</label>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] gap-[clamp(1.5rem,1rem+2vw,2.5rem)] max-w-[72rem] mx-auto px-[clamp(0.85rem,0.5rem+2vw,2rem)] pt-[clamp(3.5rem,3rem+3vw,5rem)] pb-16">
<header class="grid gap-1.5 min-w-0">
<p class="m-0 font-twr02-mono text-[0.7rem] tracking-[0.14em] uppercase text-twr02-ink2">Source bitmap · 12 × 12 px · 5-colour</p>
<h3 class="m-0 min-w-0 text-[clamp(1.25rem,1rem+1.6vw,2rem)] font-[650] leading-[1.12] tracking-[-0.02em] text-balance">Nearest-neighbour against the default sampler</h3>
</header>
<div class="grid grid-cols-[minmax(0,1fr)] gap-4 md:grid-cols-2">
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-a">
<h4 class="m-0 flex flex-wrap items-center gap-1.5 min-w-0 text-[0.78rem] font-medium text-twr02-ink2" id="twr-02-a"><span class="min-w-0 [overflow-wrap:anywhere] font-twr02-mono text-[0.68rem] px-1.5 py-0.5 rounded-sm border border-current text-twr02-warn">image-rendering: auto</span> smoothed</h4>
<ul class="list-none m-0 p-0 flex flex-wrap items-end gap-3">
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">1×</span><img class="block size-3" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at original size, smoothed"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">4×</span><img class="block w-[calc(12*var(--twr-02-px))] h-[calc(12*var(--twr-02-px))]" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at four times size, smoothed"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">8×</span><img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))]" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at eight times size, smoothed"></li>
</ul>
</article>
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-b">
<h4 class="m-0 flex flex-wrap items-center gap-1.5 min-w-0 text-[0.78rem] font-medium text-twr02-ink2" id="twr-02-b"><span class="min-w-0 [overflow-wrap:anywhere] font-twr02-mono text-[0.68rem] px-1.5 py-0.5 rounded-sm border border-current text-twr02-ok">image-rendering: pixelated</span> nearest-neighbour</h4>
<ul class="list-none m-0 p-0 flex flex-wrap items-end gap-3">
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">1×</span><img class="block size-3 twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at original size, nearest-neighbour"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">4×</span><img class="block w-[calc(12*var(--twr-02-px))] h-[calc(12*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at four times size, nearest-neighbour"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">8×</span><img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at eight times size, nearest-neighbour"></li>
</ul>
</article>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] gap-4 md:grid-cols-2">
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-t1">
<h4 class="m-0 font-twr02-mono text-[0.72rem] tracking-[0.1em] uppercase text-twr02-warn" id="twr-02-t1">Trap 1 · fractional scale</h4>
<div class="flex flex-wrap gap-4">
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite scaled by a whole number">
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">8.0× — integer, clean</figcaption>
</figure>
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<img class="block w-[calc(12px*var(--twr-02-bad)*1.4)] h-[calc(12px*var(--twr-02-bad)*1.4)] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite scaled by a fractional factor">
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">5.7× — fractional, resampled</figcaption>
</figure>
</div>
<p class="m-0 min-w-0 text-[0.78rem] leading-normal text-pretty text-twr02-ink2">Both carry <code class="font-twr02-mono text-[0.92em] bg-twr02-cell px-1 rounded-sm">[image-rendering:pixelated]</code>. The property cannot invent a fractional device pixel.</p>
</article>
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-t2">
<h4 class="m-0 font-twr02-mono text-[0.72rem] tracking-[0.1em] uppercase text-twr02-warn" id="twr-02-t2">Trap 2 · CSS-drawn shapes</h4>
<div class="flex flex-wrap gap-4">
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<span class="size-24 rounded-full bg-[#2ce8f5] border-4 border-[#0b0f14] [image-rendering:pixelated]" aria-hidden="true"></span>
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">rounded-full disc — property ignored</figcaption>
</figure>
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<span class="twr-02__stepped size-2 m-11 bg-transparent" aria-hidden="true"></span>
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">stepped box-shadow — real pixels</figcaption>
</figure>
</div>
<p class="m-0 min-w-0 text-[0.78rem] leading-normal text-pretty text-twr02-ink2"><code class="font-twr02-mono text-[0.92em] bg-twr02-cell px-1 rounded-sm">image-rendering</code> is a hint to the image sampler. A painted shape never reaches it.</p>
</article>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-twr02-bg: #efece4;
--color-twr02-bg: oklch(0.94 0.011 90);
--color-twr02-surface: #fffdf8;
--color-twr02-surface: oklch(0.99 0.006 90);
--color-twr02-cell: #e4e0d5;
--color-twr02-cell: oklch(0.90 0.013 90);
--color-twr02-ink: #191712;
--color-twr02-ink: oklch(0.19 0.01 80);
--color-twr02-ink2: #555044;
--color-twr02-ink2: oklch(0.45 0.015 80);
--color-twr02-line: #b8b2a3;
--color-twr02-line: oklch(0.77 0.017 90);
--color-twr02-acc: #b4321e;
--color-twr02-acc: oklch(0.50 0.17 32);
--color-twr02-warn: #b4321e;
--color-twr02-warn: oklch(0.50 0.17 32);
--color-twr02-ok: #1c6b3a;
--color-twr02-ok: oklch(0.47 0.12 152);
--font-twr02-sans: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-twr02-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.twr-02 {
--twr-02-px: 4px;
--twr-02-bad: 5.7;
}
/* A progressive two-declaration fallback has no utility form: two competing
utilities land in the same layer and source order decides. Keep the pair. */
.twr-02__sharp {
image-rendering: crisp-edges;
image-rendering: pixelated;
}
/* Sixty shadow layers is not a design-scale value. As one arbitrary token it
would be an unreadable multi-thousand-character class. */
.twr-02__stepped {
box-shadow: -24px -40px 0 0 #0b0f14, -16px -40px 0 0 #0b0f14, -8px -40px 0 0 #0b0f14, 0 -40px 0 0 #0b0f14, 8px -40px 0 0 #0b0f14, -40px -32px 0 0 #0b0f14, -32px -32px 0 0 #2ce8f5, -24px -32px 0 0 #2ce8f5, -16px -32px 0 0 #2ce8f5, -8px -32px 0 0 #2ce8f5, 0 -32px 0 0 #2ce8f5, 8px -32px 0 0 #2ce8f5, 16px -32px 0 0 #0b0f14, -48px -24px 0 0 #0b0f14, -40px -24px 0 0 #2ce8f5, -32px -24px 0 0 #2ce8f5, -24px -24px 0 0 #2ce8f5, -16px -24px 0 0 #2ce8f5, -8px -24px 0 0 #2ce8f5, 0 -24px 0 0 #2ce8f5, 8px -24px 0 0 #2ce8f5, 16px -24px 0 0 #2ce8f5, 24px -24px 0 0 #0b0f14, -48px -16px 0 0 #0b0f14, -40px -16px 0 0 #2ce8f5, -32px -16px 0 0 #2ce8f5, -24px -16px 0 0 #2ce8f5, -16px -16px 0 0 #2ce8f5, -8px -16px 0 0 #2ce8f5, 0 -16px 0 0 #2ce8f5, 8px -16px 0 0 #2ce8f5, 16px -16px 0 0 #2ce8f5, 24px -16px 0 0 #0b0f14, -48px -8px 0 0 #0b0f14, -40px -8px 0 0 #2ce8f5, -32px -8px 0 0 #2ce8f5, -24px -8px 0 0 #2ce8f5, -16px -8px 0 0 #2ce8f5, -8px -8px 0 0 #2ce8f5, 0 -8px 0 0 #2ce8f5, 8px -8px 0 0 #2ce8f5, 16px -8px 0 0 #2ce8f5, 24px -8px 0 0 #0b0f14, -48px 0 0 0 #0b0f14, -40px 0 0 0 #2ce8f5, -32px 0 0 0 #2ce8f5, -24px 0 0 0 #2ce8f5, -16px 0 0 0 #2ce8f5, -8px 0 0 0 #2ce8f5, 0 0 0 0 #2ce8f5, 8px 0 0 0 #2ce8f5, 16px 0 0 0 #2ce8f5, 24px 0 0 0 #0b0f14, -40px 8px 0 0 #0b0f14, -32px 8px 0 0 #2ce8f5, -24px 8px 0 0 #2ce8f5, -16px 8px 0 0 #2ce8f5, -8px 8px 0 0 #2ce8f5, 0 8px 0 0 #2ce8f5, 8px 8px 0 0 #2ce8f5, 16px 8px 0 0 #0b0f14, -32px 16px 0 0 #0b0f14, -24px 16px 0 0 #0b0f14, -16px 16px 0 0 #0b0f14, -8px 16px 0 0 #0b0f14, 0 16px 0 0 #0b0f14, 8px 16px 0 0 #0b0f14;
}
.twr-02:has(#twr-02-theme:checked) {
--color-twr02-bg: #16150f;
--color-twr02-bg: oklch(0.19 0.008 90);
--color-twr02-surface: #201e17;
--color-twr02-surface: oklch(0.25 0.01 90);
--color-twr02-cell: #2b291f;
--color-twr02-cell: oklch(0.30 0.012 90);
--color-twr02-ink: #f3f0e8;
--color-twr02-ink: oklch(0.96 0.006 90);
--color-twr02-ink2: #aca597;
--color-twr02-ink2: oklch(0.74 0.012 90);
--color-twr02-line: #454134;
--color-twr02-line: oklch(0.38 0.014 90);
--color-twr02-acc: #ff8f74;
--color-twr02-acc: oklch(0.76 0.13 36);
--color-twr02-warn: #ff8f74;
--color-twr02-warn: oklch(0.76 0.13 36);
--color-twr02-ok: #6fd396;
--color-twr02-ok: oklch(0.78 0.13 152);
}
```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 Pixel Art Scaling with Image Rendering
Source: https://codefronts.com/design-styles/tailwind-retro-ui/tailwind-pixel-art-scaling-with-image-rendering/
The same 12×12 sprite at 1×, 4× and 8× with default smoothing on the left and nearest-neighbour on the right, both sides labelled, plus the two things people get wrong: a non-integer scale factor reintroduces blur even with the property set, and the property does nothing to a shape drawn in CSS rather than sampled from a bitmap. The port has one genuinely awkward corner — a two-declaration progressive fallback has no utility form.
## HTML
```html
<section class="twr-02 relative block w-full min-h-svh box-border bg-twr02-bg text-twr02-ink font-twr02-sans" aria-labelledby="twr-02-title">
<h2 class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap" id="twr-02-title">Pixel art scaling comparison</h2>
<div class="absolute top-4 right-4 z-6">
<input class="peer absolute inset-0 size-11 m-0 opacity-0 cursor-pointer" type="checkbox" id="twr-02-theme">
<label class="grid place-items-center size-11 rounded-full border border-twr02-line bg-twr02-surface text-twr02-ink2 cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-twr02-acc peer-focus-visible:outline-offset-2" for="twr-02-theme">
<svg class="size-5" 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="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Toggle dark surround</span>
</label>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] gap-[clamp(1.5rem,1rem+2vw,2.5rem)] max-w-[72rem] mx-auto px-[clamp(0.85rem,0.5rem+2vw,2rem)] pt-[clamp(3.5rem,3rem+3vw,5rem)] pb-16">
<header class="grid gap-1.5 min-w-0">
<p class="m-0 font-twr02-mono text-[0.7rem] tracking-[0.14em] uppercase text-twr02-ink2">Source bitmap · 12 × 12 px · 5-colour</p>
<h3 class="m-0 min-w-0 text-[clamp(1.25rem,1rem+1.6vw,2rem)] font-[650] leading-[1.12] tracking-[-0.02em] text-balance">Nearest-neighbour against the default sampler</h3>
</header>
<div class="grid grid-cols-[minmax(0,1fr)] gap-4 md:grid-cols-2">
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-a">
<h4 class="m-0 flex flex-wrap items-center gap-1.5 min-w-0 text-[0.78rem] font-medium text-twr02-ink2" id="twr-02-a"><span class="min-w-0 [overflow-wrap:anywhere] font-twr02-mono text-[0.68rem] px-1.5 py-0.5 rounded-sm border border-current text-twr02-warn">image-rendering: auto</span> smoothed</h4>
<ul class="list-none m-0 p-0 flex flex-wrap items-end gap-3">
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">1×</span><img class="block size-3" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at original size, smoothed"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">4×</span><img class="block w-[calc(12*var(--twr-02-px))] h-[calc(12*var(--twr-02-px))]" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at four times size, smoothed"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">8×</span><img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))]" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at eight times size, smoothed"></li>
</ul>
</article>
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-b">
<h4 class="m-0 flex flex-wrap items-center gap-1.5 min-w-0 text-[0.78rem] font-medium text-twr02-ink2" id="twr-02-b"><span class="min-w-0 [overflow-wrap:anywhere] font-twr02-mono text-[0.68rem] px-1.5 py-0.5 rounded-sm border border-current text-twr02-ok">image-rendering: pixelated</span> nearest-neighbour</h4>
<ul class="list-none m-0 p-0 flex flex-wrap items-end gap-3">
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">1×</span><img class="block size-3 twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at original size, nearest-neighbour"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">4×</span><img class="block w-[calc(12*var(--twr-02-px))] h-[calc(12*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at four times size, nearest-neighbour"></li>
<li class="relative flex-none min-w-0 grid place-items-center gap-1.5 p-3 border border-twr02-line bg-twr02-cell [background-image:linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%),linear-gradient(45deg,rgba(0,0,0,0.05)_25%,transparent_25%_75%,rgba(0,0,0,0.05)_75%)] [background-size:12px_12px] [background-position:0_0,6px_6px]"><span class="font-twr02-mono text-[0.65rem] tracking-[0.08em] text-twr02-ink2">8×</span><img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite at eight times size, nearest-neighbour"></li>
</ul>
</article>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] gap-4 md:grid-cols-2">
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-t1">
<h4 class="m-0 font-twr02-mono text-[0.72rem] tracking-[0.1em] uppercase text-twr02-warn" id="twr-02-t1">Trap 1 · fractional scale</h4>
<div class="flex flex-wrap gap-4">
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<img class="block w-[calc(24*var(--twr-02-px))] h-[calc(24*var(--twr-02-px))] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite scaled by a whole number">
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">8.0× — integer, clean</figcaption>
</figure>
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<img class="block w-[calc(12px*var(--twr-02-bad)*1.4)] h-[calc(12px*var(--twr-02-bad)*1.4)] twr-02__sharp" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAtUlEQVR4AVyQoQ7CUAxFuycZCQgIOIJC8R84BN+JwPEfYFAESYJBDAvvNLls7ZKz3t7e7m0rlq52MvsOSWP7Lyi0vj1siHwt+gKmQhqoyieDVxCYNJfFyAANaECTIesnYIjxdC7pNfdhYfv8+Pt7st5yX63+o2nOh70BGtCAFuGE3fFkoCEa1FPDwn2zwgtkLyzwJ0K6Ntkr3fvV6ClUuC7bBtBQ94za1ayfgMBggKaCNDPpHwAAAP//UvV+hwAAAAZJREFUAwAt51rN7RbRewAAAABJRU5ErkJggg==" alt="Sprite scaled by a fractional factor">
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">5.7× — fractional, resampled</figcaption>
</figure>
</div>
<p class="m-0 min-w-0 text-[0.78rem] leading-normal text-pretty text-twr02-ink2">Both carry <code class="font-twr02-mono text-[0.92em] bg-twr02-cell px-1 rounded-sm">[image-rendering:pixelated]</code>. The property cannot invent a fractional device pixel.</p>
</article>
<article class="min-w-0 grid content-start gap-3.5 rounded border border-twr02-line bg-twr02-surface p-[clamp(0.85rem,0.6rem+1vw,1.25rem)]" aria-labelledby="twr-02-t2">
<h4 class="m-0 font-twr02-mono text-[0.72rem] tracking-[0.1em] uppercase text-twr02-warn" id="twr-02-t2">Trap 2 · CSS-drawn shapes</h4>
<div class="flex flex-wrap gap-4">
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<span class="size-24 rounded-full bg-[#2ce8f5] border-4 border-[#0b0f14] [image-rendering:pixelated]" aria-hidden="true"></span>
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">rounded-full disc — property ignored</figcaption>
</figure>
<figure class="m-0 min-w-0 grid justify-items-center gap-2 p-3.5 border border-twr02-line bg-twr02-cell">
<span class="twr-02__stepped size-2 m-11 bg-transparent" aria-hidden="true"></span>
<figcaption class="min-w-0 [overflow-wrap:anywhere] text-center font-twr02-mono text-[0.65rem] text-twr02-ink2">stepped box-shadow — real pixels</figcaption>
</figure>
</div>
<p class="m-0 min-w-0 text-[0.78rem] leading-normal text-pretty text-twr02-ink2"><code class="font-twr02-mono text-[0.92em] bg-twr02-cell px-1 rounded-sm">image-rendering</code> is a hint to the image sampler. A painted shape never reaches it.</p>
</article>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-twr02-bg: #efece4;
--color-twr02-bg: oklch(0.94 0.011 90);
--color-twr02-surface: #fffdf8;
--color-twr02-surface: oklch(0.99 0.006 90);
--color-twr02-cell: #e4e0d5;
--color-twr02-cell: oklch(0.90 0.013 90);
--color-twr02-ink: #191712;
--color-twr02-ink: oklch(0.19 0.01 80);
--color-twr02-ink2: #555044;
--color-twr02-ink2: oklch(0.45 0.015 80);
--color-twr02-line: #b8b2a3;
--color-twr02-line: oklch(0.77 0.017 90);
--color-twr02-acc: #b4321e;
--color-twr02-acc: oklch(0.50 0.17 32);
--color-twr02-warn: #b4321e;
--color-twr02-warn: oklch(0.50 0.17 32);
--color-twr02-ok: #1c6b3a;
--color-twr02-ok: oklch(0.47 0.12 152);
--font-twr02-sans: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-twr02-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.twr-02 {
--twr-02-px: 4px;
--twr-02-bad: 5.7;
}
/* A progressive two-declaration fallback has no utility form: two competing
utilities land in the same layer and source order decides. Keep the pair. */
.twr-02__sharp {
image-rendering: crisp-edges;
image-rendering: pixelated;
}
/* Sixty shadow layers is not a design-scale value. As one arbitrary token it
would be an unreadable multi-thousand-character class. */
.twr-02__stepped {
box-shadow: -24px -40px 0 0 #0b0f14, -16px -40px 0 0 #0b0f14, -8px -40px 0 0 #0b0f14, 0 -40px 0 0 #0b0f14, 8px -40px 0 0 #0b0f14, -40px -32px 0 0 #0b0f14, -32px -32px 0 0 #2ce8f5, -24px -32px 0 0 #2ce8f5, -16px -32px 0 0 #2ce8f5, -8px -32px 0 0 #2ce8f5, 0 -32px 0 0 #2ce8f5, 8px -32px 0 0 #2ce8f5, 16px -32px 0 0 #0b0f14, -48px -24px 0 0 #0b0f14, -40px -24px 0 0 #2ce8f5, -32px -24px 0 0 #2ce8f5, -24px -24px 0 0 #2ce8f5, -16px -24px 0 0 #2ce8f5, -8px -24px 0 0 #2ce8f5, 0 -24px 0 0 #2ce8f5, 8px -24px 0 0 #2ce8f5, 16px -24px 0 0 #2ce8f5, 24px -24px 0 0 #0b0f14, -48px -16px 0 0 #0b0f14, -40px -16px 0 0 #2ce8f5, -32px -16px 0 0 #2ce8f5, -24px -16px 0 0 #2ce8f5, -16px -16px 0 0 #2ce8f5, -8px -16px 0 0 #2ce8f5, 0 -16px 0 0 #2ce8f5, 8px -16px 0 0 #2ce8f5, 16px -16px 0 0 #2ce8f5, 24px -16px 0 0 #0b0f14, -48px -8px 0 0 #0b0f14, -40px -8px 0 0 #2ce8f5, -32px -8px 0 0 #2ce8f5, -24px -8px 0 0 #2ce8f5, -16px -8px 0 0 #2ce8f5, -8px -8px 0 0 #2ce8f5, 0 -8px 0 0 #2ce8f5, 8px -8px 0 0 #2ce8f5, 16px -8px 0 0 #2ce8f5, 24px -8px 0 0 #0b0f14, -48px 0 0 0 #0b0f14, -40px 0 0 0 #2ce8f5, -32px 0 0 0 #2ce8f5, -24px 0 0 0 #2ce8f5, -16px 0 0 0 #2ce8f5, -8px 0 0 0 #2ce8f5, 0 0 0 0 #2ce8f5, 8px 0 0 0 #2ce8f5, 16px 0 0 0 #2ce8f5, 24px 0 0 0 #0b0f14, -40px 8px 0 0 #0b0f14, -32px 8px 0 0 #2ce8f5, -24px 8px 0 0 #2ce8f5, -16px 8px 0 0 #2ce8f5, -8px 8px 0 0 #2ce8f5, 0 8px 0 0 #2ce8f5, 8px 8px 0 0 #2ce8f5, 16px 8px 0 0 #0b0f14, -32px 16px 0 0 #0b0f14, -24px 16px 0 0 #0b0f14, -16px 16px 0 0 #0b0f14, -8px 16px 0 0 #0b0f14, 0 16px 0 0 #0b0f14, 8px 16px 0 0 #0b0f14;
}
.twr-02:has(#twr-02-theme:checked) {
--color-twr02-bg: #16150f;
--color-twr02-bg: oklch(0.19 0.008 90);
--color-twr02-surface: #201e17;
--color-twr02-surface: oklch(0.25 0.01 90);
--color-twr02-cell: #2b291f;
--color-twr02-cell: oklch(0.30 0.012 90);
--color-twr02-ink: #f3f0e8;
--color-twr02-ink: oklch(0.96 0.006 90);
--color-twr02-ink2: #aca597;
--color-twr02-ink2: oklch(0.74 0.012 90);
--color-twr02-line: #454134;
--color-twr02-line: oklch(0.38 0.014 90);
--color-twr02-acc: #ff8f74;
--color-twr02-acc: oklch(0.76 0.13 36);
--color-twr02-warn: #ff8f74;
--color-twr02-warn: oklch(0.76 0.13 36);
--color-twr02-ok: #6fd396;
--color-twr02-ok: oklch(0.78 0.13 152);
}
```How this works
Default image scaling is a smoothing filter, and pixel art is the one case where smoothing destroys the content. Drawing a 12×12 bitmap into a 96×96 box interpolates between source pixels, so every hard edge becomes a four-pixel gradient. Correct for a photograph, catastrophic for a sprite, because in pixel art the hard edge is the artwork. image-rendering: pixelated switches the sampler to nearest-neighbour.
In Tailwind this is an arbitrary property, and that is the right answer. There is no image-rendering scale to theme — the value space is four keywords, none of them a design token:
class="block w-[calc(24*var(--twr-02-px))] [image-rendering:pixelated]"The awkward corner: pixelated and crisp-edges are not synonyms, and a utility cannot ship both. The CSS version declares crisp-edges first and pixelated second, so a newer engine takes the precise keyword and an older one still gets an edge-preserving result. That is two declarations of the same property in one rule — a progressive fallback the cascade resolves. Two competing utilities on one element do not stack that way: the compiler emits both in the same layer and source order decides, unpredictably, across builds. So the fallback pair stays a real class in the stylesheet, and the markup opts in with twr-02__sharp. This is the single most useful thing to learn from porting graphics CSS to utilities.
.twr-02__sharp {
image-rendering: crisp-edges; /* older engines */
image-rendering: pixelated; /* newer engines win */
}Integer scale factors are a correctness requirement, not a preference. At 4× every source pixel maps to exactly 4×4 device pixels. At 5.7× it maps to 5.7, which does not exist, so the rasteriser distributes the remainder and edges shimmer. Derive the display size from the source size with calc() inside the arbitrary width, and vary the multiplier, not the unit.
The other trap: image-rendering does nothing to CSS-drawn shapes. It is a hint to the image sampler, and a rounded-full disc, a gradient or a shadow never goes through the sampler — the painter draws it at final resolution and antialiases there. If you need a pixel-art shape, build it out of stepped shadows; the demo shows both side by side so the difference is not a claim but a comparison.
Long shadow lists do not belong in a class list. The stepped glyph is sixty shadow layers. As an arbitrary utility that is a single unreadable token thousands of characters long, and every edit is a re-scan of the whole string. It lives in CSS, and the markup just names it — the same judgement call as the fallback pair.
Make it yours
- Change
--twr-02-pxon the section to set the base unit; every specimen derives from it withcalc(), so the whole comparison rescales in whole pixels. - Swap the sprite by replacing the data URI in each
src; keep it small — past 32×32 it stops reading as pixel art at these scales. - Drop
twr-02__sharpfrom the right column to collapse the comparison to one condition and check what your eye was actually reading. - Change the fractional specimen from 5.7× by editing
--twr-02-bad; 3.5× misbehaves in a different, more periodic way. - Add a sprite-sheet frame swap with arbitrary
[object-position:...]steps rather than resizing the image; the rendering rule is unchanged. - Retint the specimen ground with
--color-twr02-cell— a mid grey shows edge artefacts far more clearly than white.
Gotchas — read before shipping
- A progressive two-declaration fallback cannot be expressed as two utilities. Both land in the same layer and source order decides — keep
crisp-edges/pixelatedpairs as a real class. - A non-integer scale factor reintroduces blur even with
[image-rendering:pixelated]set, because nearest-neighbour still has to resolve a fractional pixel boundary. Multiply by whole numbers. - The property does nothing for
rounded-full, a gradient or a shadow. Those never touch the image sampler. - Arbitrary values cannot contain spaces:
[background-position:0_0,6px_6px], never a literal space, or the class ends early and the checkerboard misregisters. - Device pixel ratio is not under your control. A 4× sprite on a 1.5× display is an effective 6× — still integer — but a 3× sprite becomes 4.5× and shimmers; prefer even multipliers.
- A sixty-layer shadow as one arbitrary token compiles, but it is unmaintainable and blows past sane class-attribute length. Put it in CSS.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
image-rendering: pixelated lands at Chrome 41 / Safari 10 / Firefox 93, so the sprite comparison itself runs far back. Tailwind v4's generated sheet is the real floor at Chrome 111+, Safari 16.4+ and Firefox 128+. Engines below Firefox 93 take the crisp-edges declaration in the fallback class and get an edge-preserving upscale rather than nearest-neighbour.