shadcn.io is not affiliated with official shadcn/ui
Dither Gradient Shader draws a gradient the way a 1-bit screen would. Three soft orbs, a warm vermilion one, a deep ink-violet one and a glow of bare paper, drift and cross on warm paper along slow Lissajous paths, bent by a little noise. Every pixel is then snapped to a chunky 3-pixel grid and to a handful of flat inks by an ordered 8×8 Bayer dither, so the blends become the cross-hatched textures of a classic Mac. The orbs fade to bare paper at their edges and ease off in the middle, which stays calm for text. A WGSL fragment shader on WebGPU computes it each frame, for about 0.15 ms of GPU time.
Use it behind an indie product's hero, a pricing card, a game or a zine. For round print dots sized by tone instead, use the Halftone background; for four-color print with rosettes, CMYK Halftone Shader. It runs only while it's on screen, holds a still frame with reduced motion, and shows a CSS gradient in the same inks in browsers without WebGPU.
Installation
Usage
import {
DitherGradientShader,
} from "@/components/ui/dither-gradient-shader";<DitherGradientShader className="flex min-h-[32rem] items-center justify-center">
<h1 className="font-mono text-5xl font-semibold text-[#1d1a2f]">Made by hand</h1>
</DitherGradientShader>Dither Gradient Shader is a container: your content sits on top of the dither, in normal flow. It fills its own box, so give it a height or content. For a backdrop behind the whole page, render it empty with className="fixed inset-0 -z-10". The paper is light in both color schemes, so use dark text, ideally the ink color: it reads at 14:1 on the paper and 5.5:1 on the vermilion, but not on the ink itself. Keep copy in the calm middle, or put it on a paper panel such as bg-[#efe9dc]/80, which keeps ink text above 9:1 everywhere.
Demos
One bit, in a card
levels={2} drops the vermilion and leaves two inks, paper and ink: a true 1-bit screen, where every shade is a pattern of ink pixels.
Fine
pixelSize={1} makes each dither pixel one CSS pixel, a crisp, fine texture on a retina screen, and levels={5} adds two mixed inks, between paper and vermilion and between vermilion and ink, for smoother steps.
Performance
About 0.15 ms of GPU time a frame at 1280×720 on an M2 Max laptop; slower GPUs take several times longer. pixelSize and levels don't change the cost. Keep the default quality: the dither is made of hard pixel edges, and half resolution would blur them. The animation stops when the shader scrolls out of view, and the browser pauses it in a hidden tab. Every shader on a page shares one GPU device and one frame loop, and vgpu (about 50 KB gzipped) loads only when the first one mounts.
Browser support
The shader needs WebGPU: Chrome and Edge, Safari 26 and later, and Firefox on Windows and macOS. Elsewhere, and until the first frame, the box shows soft CSS orbs of the same inks under a faint checker, so the layout never jumps. data-state on the root reads unsupported there, if you want to style around it. See Shader Canvas.
Accessibility
The dither is decoration: it's hidden from assistive tech and lets every click through to your content. With reduced motion it draws one still frame and stops. Nothing flashes: the orbs take half a minute or more to cross the screen, and the dither pattern is fixed to the screen, so only the pixels at an orb's moving edge change, a few at a time; measured over 6 seconds with WCAG's area test, and even in 32-pixel patches, it never flashes. Use dark text, as in Usage.
FAQ
What is ordered dithering?
A way to show shades with only a few flat colors. Each pixel compares the gradient's value against its own entry in an 8×8 grid of thresholds, the Bayer matrix, and takes the ink above or below. The grid repeats, so a mid shade becomes a regular cross-hatch. The thresholds are computed from each pixel's coordinates, bit by bit, with no texture.
What does pattern change?
pattern="bayer", the default, gives the regular cross-hatches of an old Mac. pattern="noise" swaps in interleaved gradient noise, a fine, even scatter with no visible grid.
Can I change the colors?
Yes: paperColor, accentColor and inkColor take any CSS color, var(--primary) included, and follow a theme switch. The ramp runs paper, then accent, then ink, so keep it in that order of lightness for the orbs to read as shading. With levels={2} only paper and ink are used.
How do the levels work?
levels is the number of inks, paper included, from 2 to 5. Three gives paper, vermilion and ink; four and five add mixes between them, for gentler steps. pixelSize sets how big each dither pixel is, from 1 to 8 CSS pixels.
API Reference
DitherGradientShader
data-slot="dither-gradient-shader"
accentColor?string
The middle ink, between paper and ink: the warm orb. Any CSS colour. @default "#ff5b3a".
- Default
"#ff5b3a"
fallback?string
What shows until the first frame and in browsers without WebGPU: a CSS
background value (gradients, sizes, a colour) that looks like the
shader. It can use the colour props' --shader-<field> variables.
inkColor?string
The darkest ink: the deep orb. Any CSS colour. @default "#1d1a2f".
- Default
"#1d1a2f"
intensity?number
How much of the paper the orbs cover, from 0 (bare paper) to 2. @default 1.
- Default
1
interactive?boolean
Whether u.pointer follows the pointer. @default false.
- Default
false
levels?number
How many inks the dither uses, paper included, from 2 (one bit: paper and ink) to 5. @default 3.
- Default
3
paperColor?string
The paper. Any CSS colour. @default "#efe9dc".
- Default
"#efe9dc"
pattern?keyof typeof PATTERNS
The dither: an ordered 8×8 Bayer grid of cross-hatches, or a fine, even noise. @default "bayer".
- Default
"bayer"
paused?boolean
Holds the current frame. @default false.
pixelSize?number
The size of a dither pixel, from 1 to 8 CSS pixels. @default 3.
- Default
3
quality?number
Render scale, from 0.25 to 1, of the screen's pixels (capped at 2×). @default 1.
speed?number
Animation speed: 1 is normal, 0 holds the frame. @default 1.
time?number
A fixed moment, in seconds: draws it and doesn't animate.
render?ReactElement | ((props: object, state: object) => ReactElement)
Replace the host element or compose it with another component.
- Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string
Forwarded to the part.
Related
- Pro


CMYK Halftone Shader
Digital & retroFour-color print with rosettes
- Pro


ASCII Sculpture Shader
Digital & retroAnother retro look, in amber glyphs
- Pro


CRT Phosphor Shader
Digital & retroAnother retro screen, glowing
- Pro


Binary Shader
Digital & retroA grid of living digits


Light Leak Shader
Light & glassWarm, analog grain, free


Orb Grid Shader
GeometrySoft orbs under a grid, free