# Dither text

A display headline whose letters are filled with a dithered gradient, noise, dots or hatching, in any palette colour, from hero size down to section headings.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The dithercn agent skill, for building any dither on the engine, is at [/.well-known/agent-skills/dithercn/SKILL.md](/.well-known/agent-skills/dithercn/SKILL.md).

> **This is an example, not the only option.** It is one component built on the dithercn engine. Install it when it is what you were asked for. For any other look, don't stretch its props to fit: write your own field on `DitherShader` ([Write your own dither](/docs/engine/write-your-own.md)), and read this example's source for technique. Its full source is in [https://dithercn.dev/r/dither-text.json](https://dithercn.dev/r/dither-text.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://dithercn.dev/r/dither-text.json
```

## Usage [#usage]

```tsx
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. `direction` is the way it thins out: `"down"` is solid at the top.
* `noise` fills the letters with drifting blobs of density. `scale` sets their size and `direction` the way they drift.
* `dotted` and `hatched` use the same textures as the charts and buttons, drifting a cell at a time in `direction`. A faint `ghost` of the ink fills the gaps so the letters keep their shape.
* `solid` is flat colour, useful with `bloom` alone.

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 [#examples]

### Hatched and dotted [#hatched-and-dotted]

Two words in heavy type, one `hatched` in orange with a low bloom and one `dotted` in pink drifting left.

```tsx title="examples/dither-text-hatched.tsx"
import { DitherText } from "@/components/dithercn/dither-text";

export default function DitherTextHatched() {
  return (
    <section className="flex w-full max-w-2xl flex-col items-center gap-5 px-6 text-center">
      <span className="text-muted-foreground font-mono text-xs tracking-[0.16em] uppercase">
        Launch week · Day 3
      </span>
      <h3 className="flex flex-col text-6xl leading-[0.88] font-black tracking-[-0.06em] sm:text-8xl">
        <DitherText fill="hatched" color="orange" bloom="low" cell={3}>
          Ship
        </DitherText>
        <DitherText fill="dotted" color="pink" direction="left" cell={3}>
          louder
        </DitherText>
      </h3>
      <p className="text-muted-foreground max-w-sm text-sm text-pretty">
        Hatched and dotted fills with a faint ghost between the lines, so the
        letters keep their shape at any cell size.
      </p>
    </section>
  );
}
```

### Noise with a palette [#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.

```tsx title="examples/dither-text-noise.tsx"
import { DitherText } from "@/components/dithercn/dither-text";

export default function DitherTextNoise() {
  return (
    <div className="flex flex-col items-center gap-4 px-6 text-center">
      <DitherText
        as="h3"
        fill="noise"
        scale={2.4}
        from={0.35}
        to={1}
        direction="right"
        matrix="blue-noise"
        palette={["transparent", "blue", "purple"]}
        className="text-5xl leading-none font-bold tracking-[-0.05em] sm:text-7xl"
      >
        Weather report
      </DitherText>
      <p className="text-muted-foreground max-w-sm text-sm text-pretty">
        Blue noise and a palette from blue to purple give the letters a soft,
        grainy drift instead of a grid.
      </p>
    </div>
  );
}
```

### Section heading in the foreground colour [#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.

```tsx title="examples/dither-text-section.tsx"
import { DitherText } from "@/components/dithercn/dither-text";

export default function DitherTextSection() {
  return (
    <section className="flex w-full max-w-lg flex-col gap-3 px-6">
      <span className="text-muted-foreground font-mono text-xs tracking-wider uppercase">
        Changelog · October
      </span>
      <DitherText
        as="h3"
        direction="right"
        from={1}
        to={0.55}
        color="--foreground"
        animate={false}
        className="text-3xl leading-tight font-semibold tracking-[-0.035em] text-balance sm:text-4xl"
      >
        Exports land in every format
      </DitherText>
      <p className="text-muted-foreground text-sm text-pretty">
        PNG, SVG and CSS exports from the maker, with the palette and cell size
        you picked. The heading thins out left to right in the page&apos;s own
        foreground colour, so it works on light and dark themes.
      </p>
    </section>
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` |  | The text. It stays real, selectable text. |
| `as` | `"h1" \| "h2" \| "h3" \| "h4" \| "p" \| "span" \| "div" \| "strong"` | `"span"` | The element to render. |
| `fill` | `"gradient" \| "noise" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | What fills the letters. |
| `color` | `DitherColor` | `"ink"` | Ink colour: a palette name such as "green" or any CSS colour. |
| `direction` | `"down" \| "up" \| "left" \| "right" \| number` | `"down"` | 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. |
| `scale` | `number` | `3` | Noise only. Higher values give smaller blobs. |
| `from` | `number` | `1` | Gradient and noise: tone at the dense end, from 0 (no ink) to 1 (solid). |
| `to` | `number` | `0.4` | Gradient and noise: tone at the sparse end. Keep it above about 0.3 so the letters stay readable. |
| `ghost` | `number` | `0.2 for dotted and hatched, else 0` | Opacity of the ink in the gaps between dots, 0 to 1. |
| `bloom` | `"off" \| "low" \| "high"` | `"off"` | A soft glow in the ink colour that follows the dots. |
| `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` |  | Colours from the lightest tone to the darkest, for a multi-tone fill. Overrides color and ghost. |
| `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern for gradient and noise. See Dither engine. |
| `cell` | `number` | `Font size / 24, at least 2` | Size of one dither cell in CSS pixels. |
| `animate` | `boolean` | `true` | false draws the fill once and stops. |
| `speed` | `number` | `1` | How fast the fill drifts. 0 holds it still. |

## Notes [#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. `solid` never animates.