components

Dither

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.

tsx
<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} />
algorithm
alt
height
pixelSize
src
width

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:

tsx
<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:

tsx
<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-proxy in the repo for a minimal example.

Props

PropTypeDefaultDescription
algorithmthreshold | bayer2 | bayer4 | bayer8 | floyd-steinberg | atkinsonfloyd-steinberg
alt *textRequired — the canvas this renders as has no text of its own.
classNametext
heightnumber
invertboolean
levelsnumber2
pixelSizenumber2Downsample factor before dithering — bigger is chunkier.
src *text
widthnumberDefaults to the image's natural size once loaded.