Runs an image through a dithering algorithm (Floyd–Steinberg, Atkinson, Bayer, or a hard
threshold) and paints it to a canvas at a deliberately reduced resolution (pixelSize), stretched
back up via CSS with image-rendering: pixelated — that stretch, not a second manual redraw, is
what turns the dithered result blocky.
Doubles as its own SSR-safe placeholder: the real draw only happens client-side, after mount.
<Dither algorithm="floyd-steinberg" alt="Demo gradient" height={96} pixelSize={2} src="data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2296%22%20height%3D%2296%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%221%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23000%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23fff%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%2296%22%20height%3D%2296%22%20fill%3D%22url(%23g)%22%2F%3E%3Ccircle%20cx%3D%2230%22%20cy%3D%2266%22%20r%3D%2220%22%20fill%3D%22%23000%22%2F%3E%3Ccircle%20cx%3D%2270%22%20cy%3D%2230%22%20r%3D%2214%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E" width={96} />Examples
algorithm trades noise pattern for character — floyd-steinberg (the default) diffuses error
smoothly and reads closest to a photograph; the bayer variants use a fixed repeating matrix
instead, giving a more uniform, screen-like texture:
<Dither src={photo} alt="Portrait" algorithm="atkinson" levels={2} />
<Dither src={photo} alt="Portrait" algorithm="bayer4" levels={4} />levels raises the output above pure black/white — useful when the source has enough tonal range
that 2 levels loses too much:
<Dither src={photo} alt="Landscape" levels={4} pixelSize={1} />If src is an image your own server doesn't control, see Loading external
images below — the browser needs the source to grant CORS before this
component can read its pixels back out at all.
Loading external images
Both Dither and Halftone draw the source image to an offscreen canvas and read its pixels back
out — a browser only allows that for a cross-origin image if its server sends a CORS header
(Access-Control-Allow-Origin). Without one, the canvas is "tainted" and reading pixels throws, so
both components catch that and fall back to rendering the plain, un-dithered <img> instead of a
broken canvas. Two ways to actually get the effect on an external image:
- Serve it from your own origin (or any host that already sends permissive CORS headers — many image CDNs do).
- Proxy it through your own backend so the browser sees it as same-origin. This site's own
Playground does exactly that for the demo above — see
apps/docs/src/app/api/image-proxyin the repo for a minimal example.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| algorithm | threshold | bayer2 | bayer4 | bayer8 | floyd-steinberg | atkinson | floyd-steinberg | — |
| alt * | text | — | Required — the canvas this renders as has no text of its own. |
| className | text | — | — |
| height | number | — | — |
| invert | boolean | — | |
| levels | number | 2 | — |
| pixelSize | number | 2 | Downsample factor before dithering — bigger is chunkier. |
| src * | text | — | — |
| width | number | — | Defaults to the image's natural size once loaded. |