Installation
pnpm dlx shadcn@latest add https://dithercn.dev/r/dither-text.jsonUsage
import { DitherText } from "@/components/dithercn/dither-text";
export function Hero() {
return (
<DitherText
as="h1"
color="blue"
className="text-8xl font-bold tracking-tight"
>
Your headline
</DitherText>
);
}The text stays a real element: people can select and copy it, screen readers read it, and search engines index it. The dither is a small image drawn on the client and clipped to the letters with background-clip: text.
fill picks what goes inside the letters:
gradient(the default) runs from solid ink on one side to a sparse dither on the other, with a slow drift that bends the bands.directionis the way it thins out:"down"is solid at the top.noisefills the letters with drifting blobs of density.scalesets their size anddirectionthe way they drift.dottedandhatcheduse the same textures as the charts and buttons, drifting a cell at a time indirection. A faintghostof the ink fills the gaps so the letters keep their shape.solidis flat colour, useful withbloomalone.
Set the size with your own classes. The cell size follows the font size, about 1/24 of it and never below 2px, so the same component reads well as a hero and as a section heading. Pass cell to fix it.
Examples
Hatched and dotted
Two words in heavy type, one hatched in orange with a low bloom and one dotted in pink drifting left.
Noise with a palette
palette takes over from color for a multi-tone fill. Here blue noise and a palette from transparent through blue to purple give a soft grain.
Section heading in the foreground colour
A smaller heading that thins out to the right, drawn in --foreground so it matches body text on light and dark themes. to={0.55} keeps the last word readable and animate={false} draws it once.
Props
| Prop | Type | Default |
|---|---|---|
| children* The text. It stays real, selectable text. | Type: ReactNode | Default – |
| as The element to render. | Type: "h1" | "h2" | "h3" | "h4" | "p" | "span" | "div" | "strong" | Default "span" |
| fill What fills the letters. | Type: "gradient" | "noise" | "dotted" | "hatched" | "solid" | Default "gradient" |
| color Ink colour: a palette name such as "green" or any CSS colour. | Type: DitherColor | Default "ink" |
| direction Gradient: the way it thins out from its solid side. Noise, dotted and hatched: the way it drifts. A number is an angle in degrees, as in CSS. | Type: "down" | "up" | "left" | "right" | number | Default "down" |
| scale Noise only. Higher values give smaller blobs. | Type: number | Default 3 |
| from Gradient and noise: tone at the dense end, from 0 (no ink) to 1 (solid). | Type: number | Default 1 |
| to Gradient and noise: tone at the sparse end. Keep it above about 0.3 so the letters stay readable. | Type: number | Default 0.4 |
| ghost Opacity of the ink in the gaps between dots, 0 to 1. | Type: number | Default 0.2 for dotted and hatched, else 0 |
| bloom A soft glow in the ink colour that follows the dots. | Type: "off" | "low" | "high" | Default "off" |
| palette Colours from the lightest tone to the darkest, for a multi-tone fill. Overrides color and ghost. | Type: [DitherColor, DitherColor, ...DitherColor[]] | Default – |
| matrix The threshold pattern for gradient and noise. See Dither engine. | Type: "bayer2" | "bayer4" | "bayer8" | "blue-noise" | Default "bayer4" |
| cell Size of one dither cell in CSS pixels. | Type: number | Default Font size / 24, at least 2 |
| animate false draws the fill once and stops. | Type: boolean | Default true |
| speed How fast the fill drifts. 0 holds it still. | Type: number | Default 1 |
Notes
- The text is the accessible content. The fill is a background image, so assistive technology ignores it.
- Before the client draws the fill, the letters show in the ink colour through a plain dither mask, so there is no flash of unstyled text. In forced-colours mode the fill is removed and the text uses the system colour.
- Selected text shows in the site's selection colours, solid, so it stays readable while highlighted.
- With reduced motion turned on, or
animate={false}, it draws one frame. It redraws at 12 fps only while on screen, at one pixel per dither cell, and pauses in hidden tabs.solidnever animates.