# Moiré

Two fine gratings laid over each other and sliding slowly, so huge interference bands bloom, sweep and turn across the box, as when two window screens overlap.

> 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/moire.json](https://dithercn.dev/r/moire.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://dithercn.dev/r/moire.json
```

## Usage [#usage]

```tsx
import { Moire } from "@/components/dithercn/moire";

export function Hero() {
  return (
    <section className="relative isolate min-h-[480px]">
      <Moire className="absolute inset-0 -z-10" />
      <h1>Your headline</h1>
    </section>
  );
}
```

Two gratings of fine lines sit on top of each other. Each line is a whole number of dither cells wide, every `pitch` cells, so the lines stay crisp at any size. A line of one grating is drawn in the second palette colour, and a cell where lines of both gratings cross is drawn in the last. Where the gratings line up, their lines overlap into crisp ink stripes on open paper. Where they fall half a pitch apart, they fill each other's gaps with a flat tint. Between the two, the stripes thicken and soften, and the eye reads the change as broad bands many times larger than the lines themselves.

The bands move far more than the lines do. The gratings slide apart by a fraction of a cell per second, and the bands sweep across the box at dozens of pixels per second. At the same time the angle between the gratings breathes, so the bands swell, narrow and swing. The second grating is never quite flat: `warp` bends it by a cell or two in a few broad swells that change shape slowly. That bend is too small to see in the lines, but it curves the bands by most of their width. There are three variants:

* `"lines"`: two sets of straight lines a few degrees apart. The bands run across the lines.
* `"rings"`: two sets of concentric rings whose centres drift apart and together. The bands fan out from the middle as a slowly turning starburst.
* `"mesh"`: two square grids, like a pair of window screens. The bands form a large lattice of their own.

`bands` sets how many bands cross the box, not how far the lines are turned, so a phone shows the same composition as a desktop.

## Examples [#examples]

### Rings in a palette [#rings-in-a-palette]

`colors` gives each band the next colour from a list in turn. The lines that cross take the band's colour and the rest a tint of it, and the colours change halfway between two bands, inside the flat tint, so no colour edge cuts across the crisp stripes. With `"rings"`, the bands fan out from the middle, so the colours do too. Ring colours run into each other across most of each band, and they fade towards grey near the middle, where every band meets, so the centre shows the rings' own interference rather than slices of colour.

```tsx title="examples/moire-rings.tsx"
import { Moire } from "@/components/dithercn/moire";

export default function MoireRings() {
  return (
    <Moire
      variant="rings"
      bands={4}
      angle={70}
      colors={["blue", "purple", "pink", "orange"]}
      className="absolute inset-0"
    />
  );
}
```

### Behind a headline [#behind-a-headline]

`mask="center"` clears an oval in the middle of the box, and the lines dissolve into dots towards it. Two big bands and two soft tints of the ink keep the pattern quiet behind the text.

```tsx title="examples/moire-headline.tsx"
import { Moire } from "@/components/dithercn/moire";

export default function MoireHeadline() {
  return (
    <section className="relative isolate flex size-full flex-col items-center justify-center gap-3 px-6 text-center">
      <Moire
        bands={2}
        angle={115}
        mask="center"
        palette={[
          "--dither-paper",
          "color-mix(in oklch, var(--dither-ink) 18%, var(--dither-paper))",
          "color-mix(in oklch, var(--dither-ink) 55%, var(--dither-paper))",
        ]}
        className="absolute inset-0 -z-10"
      />
      <h3 className="max-w-sm text-3xl font-medium tracking-[-0.03em] text-balance">
        Two layers, one picture
      </h3>
      <p className="text-muted-foreground max-w-[18rem] text-sm text-pretty">
        Sync your design files and your code, and see where they drift apart
        before your users do.
      </p>
    </section>
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"lines" \| "rings" \| "mesh"` | `"lines"` | Two sets of straight lines, two sets of concentric rings, or two square grids. |
| `pitch` | `number` | `4` | Spacing of each grating's lines, in whole dither cells, from 3 to 12 (4 to 12 for mesh). Each line is half a pitch wide. |
| `bands` | `number` | `3` | Roughly how many interference bands cross the box, from 1 to 8. Fewer bands are bigger. |
| `angle` | `number` | `30` | Direction of the lines in degrees, as in CSS: 0 runs up, 90 across. For rings, the direction from one ring centre to the other. |
| `center` | `[x: number, y: number]` | `[0.5, 0.5]` | The point the gratings turn around, or the midpoint between the ring centres, as fractions of the box from the top left. |
| `drift` | `number` | `1` | How fast the gratings slide against each other, so the bands sweep across the box. 0 stops the slide; the bands still turn and breathe. |
| `warp` | `number` | `0.6` | How much the second grating bends, so the bands curve and twist instead of running straight, from 0 (flat) to 2. |
| `mask` | `"none" \| "center" \| "top" \| "bottom" \| "edges"` | `"none"` | Fades it to paper where your text sits. |
| `colors` | `[DitherColor, ...DitherColor[]]` | `–` | Colour each band from this list in turn. Palette names such as "blue", CSS variable names, or any CSS colour. The palette then only sets the paper. |
| `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, a tint of --dither-ink, then --dither-ink` | Colours from paper to ink. Lines of one grating take the colour halfway along, cells where both cross the last. With two colours, lines that don't cross dither to half. |
| `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer8"` | The threshold pattern, seen where a mask fades the lines. See Dither engine. |
| `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels, and so of the finest line. |
| `speed` | `number` | `1` | How fast everything moves. |
| `animate` | `boolean` | `true` | false stops the motion: a still frame if it starts off, the current frame if it was moving. |
| `className` | `string` | `–` | Size and position. The gratings fill this box. |

## Notes [#notes]

* It 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 draws the same two gratings as CSS repeating gradients, in the palette's middle colour, with the last colour where their lines cross, so the moiré still appears. An SVG displacement filter bends the second grating by `warp`, so the bands curve, though not in exactly the same shape as the shader's. With `colors`, the colour stripes stay straight while the bands bend, and the ring colours blend smoothly instead of as dots.
* Each line is drawn in whole cells and every cell is either on a line or not, so the fine pattern never blurs into grey and never flickers. No line moves more than a cell every few seconds; only the bands move quickly.
* It costs a few dot products per cell, at 30 fps and one pixel per dither cell. See [Performance](/docs/engine/performance).