Installation
pnpm dlx shadcn@latest add https://dithercn.dev/r/halftone.jsonUsage
import { Halftone } from "@/components/dithercn/halftone";
export function Hero() {
return (
<section className="relative isolate min-h-[480px]">
<Halftone color="blue" className="absolute inset-0 -z-10" />
<h1>Your headline</h1>
</section>
);
}Each square of a rotated screen holds one round dot, sized by the source at its centre, the way a printing screen works. Dot edges are dithered at the screen's own device pixels, so dots stay round and grow smoothly from a speck to touching their neighbours. The screen slides slowly along its angle (drift), so dots swell and shrink as they cross the source.
Pass channels instead of color for two or three inks. Each gets its own screen, turned 30° from the last, and overlapping dots mix their colours. With blue, pink and orange you get the rosettes of a CMYK print.
Examples
Spotlight behind a headline
source="radial" grows the dots towards center. With the centre below the bottom edge, a radius under 1 and a transparent background, the dots swell up from the bottom of the section and run out before the text.
Duotone ramp in a card
source="linear" grows the dots towards direction. Two channels at a shallow 15° screen, held still, and faded in from the left with a CSS mask.
Three-ink print
A coarse 16px screen in three inks behind an editorial block.
Props
| Prop | Type | Default |
|---|---|---|
| color Colour of the dots: a palette name such as "blue", a CSS variable name, or any CSS colour. | Type: DitherColor | Default "ink" |
| channels Two or three inks on separate screens, 30° apart. Overlaps mix. Use instead of color. | Type: [DitherColor, DitherColor, DitherColor?] | Default – |
| background Colour behind the dots. "transparent" lays the screen over the content underneath. | Type: DitherColor | Default "--dither-paper" |
| source What sizes the dots: soft drifting clouds, distance from a point, or a ramp. | Type: "noise" | "radial" | "linear" | Default "noise" |
| center Radial only. Where the dots are largest, as fractions of the box from the top left. | Type: [x: number, y: number] | Default [0.5, 0.5] |
| radius Radial only. Where the dots run out. 1 reaches the farthest corner. | Type: number | Default 1 |
| direction Linear only. Direction the dots grow towards, in degrees as in CSS. | Type: number | Default 0 |
| size Distance between dot centres in CSS pixels. | Type: number | Default 10 |
| angle Screen angle in degrees. Each extra channel adds 30°. | Type: number | Default 45 |
| intensity Size of the largest dot, 0 to 1. At 1 the biggest dots just touch. | Type: number | Default 0.9 |
| scale Noise only. Size of the clouds. | Type: number | Default 1 |
| drift How fast the screen slides along its angle. 0 holds the screen still. | Type: number | Default 1 |
| speed How fast the source moves. | Type: number | Default 1 |
| animate false draws one still frame. | Type: boolean | Default true |
| cell Size of one rendered pixel in CSS pixels. Larger cells give blockier dots. | Type: number | Default one device pixel |
| className Size and position. The halftone fills this box. | Type: string | Default – |
Notes
- The halftone is decorative and hidden from assistive technology. Put real text on top of it, not inside it.
- With reduced motion turned on, it draws one still frame and stops.
- It pauses when scrolled out of view or when the tab is hidden, and releases its WebGL context when far offscreen. Without WebGL it shows a CSS dot screen in the same inks.
- Overlap colours are mixed with CSS
color-mixin oklch, so they follow the theme along with the inks.