# dithercn: full documentation > Add dithers to anything in your app. dithercn gives your coding agent the primitives: a dither engine and a skill that teaches it. Ask for any dither and your agent builds it on the engine, in your shadcn theme. The backgrounds, charts and interface pieces in the docs are examples. ## Start here dithercn is an engine, not a component menu. The components under Backgrounds, Charts, Interface, Text and Images are examples of what the engine draws. When a user asks for a dither, build the one they asked for: 1. Install the engine and the agent skill: `npx shadcn@latest add https://dithercn.dev/r/dithercn-skill.json`. Then follow the skill (https://dithercn.dev/.well-known/agent-skills/dithercn/SKILL.md). 2. Write your own field on `DitherShader`: [Write your own dither](https://dithercn.dev/docs/engine/write-your-own.md). A field is a GLSL function that returns a tone from 0 to 1; any shape you can describe with math works. 3. Colour it from the theme: [Palettes](https://dithercn.dev/docs/engine/palettes.md). For many hues at once (a rainbow, RGB, a different colour per line or shape), add a `colorField` next to the field; the "Any number of hues" section shows how. 4. Install an example only when it is what the user asked for. Otherwise read the closest one's source for technique and write your own. # Introduction A dither engine your coding agent uses to build any dither for your landing page or dashboard. > 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). dithercn is a dither engine with an agent skill. Ask your coding agent for a dither, say "a dithered topographic background behind the hero" or "a radial usage meter for the billing card", and it builds that component on the engine, in your shadcn theme, with motion, light and dark mode and reduced motion handled. The components in these docs are examples of what the engine can make. Install one when it fits, read its source when you want to see how it was done, and ask your agent for something new when it doesn't. ## What you get [#what-you-get] * **The engine.** `dither-core` installs into `lib/dither` as plain source. It turns a tone you compute into dither dots: a WebGL shader for anything that moves or varies per pixel, SVG patterns and CSS masks for static fills, image dithering, and export to PNG, SVG and CSS. * **The agent skill.** A `SKILL.md` and references that teach Claude Code, Codex, Cursor and other agents the engine: how to pick a technique, write a field, place it on a page, colour it from your theme and check it in a browser. [Installation](/docs/installation) adds it to your project. * **The examples.** More than forty backgrounds, charts, interface pieces, text effects and image treatments, each installable with `npx shadcn add`. Browse them in the [catalog](/docs/examples). ## What a dither is [#what-a-dither-is] A dither draws a smooth tone using only a few colours. It places dots of ink at a density that matches the tone, so your eye blends them into a gradient. Dithering began as a workaround for limited screens and printers. It now reads as a deliberate texture, a little like risograph print, and it suits both a hero that should feel crafted and a dashboard that should feel calm. ## Ask your agent for a dither [#ask-your-agent-for-a-dither] Once the skill is installed, describe what you want and where it goes. Name the page, the mood and anything that must stay readable: * "Add a dithered topographic contour background behind the hero on the home page. Slow, quiet, readable headline." * "On the billing page, add a dithered radial meter for storage used, 72%, with the number in the middle." * "Make the pricing section's top edge dissolve into the page with a dithered fade in our accent colour." * "Dither the team photos in the about page in our primary colour, and make them sharpen on hover." * "Replace the empty state on the inbox with a small dithered illustration of a paper plane." You don't need to know which component or API fits. The skill sends the agent through four steps: 1. **Set up the engine.** It checks for `lib/dither` and installs `dither-core` if it's missing. 2. **Pick a technique.** A WebGL field for anything that moves or varies per pixel, SVG patterns for chart and meter fills, CSS masks for blocks and bars, image dithering for photos, and export for files. It reads the closest example in the [catalog](/docs/examples) for technique, and installs one only when it is exactly what you asked for. 3. **Build the component.** It writes a typed component with props you can tweak, places it behind or inside your content, and colours it from your theme. 4. **Check it in a browser.** It looks at the result in light and dark mode, at desktop and phone widths and with reduced motion, and fixes what doesn't read. ## How the docs are organised [#how-the-docs-are-organised] * **Get started:** this page, [Installation](/docs/installation) and [Theming](/docs/theming). * **Examples:** the [catalog](/docs/examples) of every component, then one page per component, grouped into Backgrounds, Charts, Interface, Text and Images. * **Engine:** how `dither-core` works, for when you want to build a dither yourself. Start with the [dither engine](/docs/engine/dither-engine) and [Write your own dither](/docs/engine/write-your-own). ## Principles [#principles] * **Your theme, not ours.** Dithers read your `--primary` and `--background` tokens through the [`--dither-*` variables](/docs/theming), so they change when your theme changes. * **Quiet by default.** Motion is slow, and every animated dither holds still when the visitor prefers reduced motion. * **Cheap on dashboards.** Static fills are SVG patterns and CSS masks, and shaders draw one pixel per cell and stop when they scroll out of view. * **Real text stays text.** Dithered headings and labels are still HTML, so they can be selected, read aloud and indexed. * **Source you own.** Everything installs as files in your project. You and your agent can read and change all of it. ## Credits [#credits] [Dither Kit](https://www.tripwire.sh/dither-kit) by Boring Software set the bar for this project. The fill variants, bloom, named colours, scrubbing chart tooltip, legend spotlight and lock, docs knobs, generative avatars and the button that thickens its dither on hover all started as ideas we saw there. The code and the look are our own. Dither Kit credits [EvilCharts](https://evilcharts.com) for the dithered chart style it grew from, so thanks to both. # Installation Add the dithercn engine and agent skill to a shadcn project, or install a single example. > 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). dithercn installs with the shadcn CLI, like any other registry. One command adds the engine and the agent skill; after that you ask your agent for dithers, or install examples one by one. ## Quick start with your agent [#quick-start-with-your-agent] Paste this prompt into Claude Code, Codex, Cursor or any other coding agent. It installs the engine and the skill, reads the docs and builds the dither you ask for. It's the same prompt the home page copies. ```text Add dithercn to this project and build me a dither with it. 1. Install the dithercn engine and agent skill with the shadcn CLI: npx shadcn@latest add https://dithercn.dev/r/dithercn-skill.json This writes lib/dither, the skill in .agents/skills/dithercn and .claude/skills/dithercn, and the --dither-* CSS variables. The project needs shadcn/ui, Tailwind CSS v4 and React 19. 2. Read .agents/skills/dithercn/SKILL.md and follow it for everything below. If it isn't on disk, read https://dithercn.dev/.well-known/agent-skills/dithercn/SKILL.md. The docs are at https://dithercn.dev/docs, and https://dithercn.dev/llms.txt lists every page as Markdown. 3. If I haven't said which dither I want and where it goes, ask me in one question, and suggest two or three that would suit this project. 4. Build the dither I asked for on the engine, coloured from my theme. The example components are references: install one only if it is exactly what I asked for. Otherwise write your own field on DitherShader and borrow technique from the closest example's source. 5. Check it in a browser in light and dark mode, at desktop and phone widths and with reduced motion, and fix what doesn't read. Then tell me what you made and where. ``` The rest of this page does the same steps by hand. ## Before you start [#before-you-start] You need a project that already uses shadcn/ui, with a `components.json` file, Tailwind CSS v4 and React 19. If you don't have one yet, follow the [shadcn installation guide](https://ui.shadcn.com/docs/installation) first. ## Add the skill with the skills CLI [#add-the-skill-with-the-skills-cli] The [skills CLI](https://skills.sh) installs the skill into Claude Code, Codex, Cursor, Gemini CLI and other agents that read skills. Run it in your project: ```bash npx skills add https://dithercn.dev ``` It asks which agents to install for, then writes the skill and its references into each agent's skills folder, such as `.claude/skills/dithercn/` and `.agents/skills/dithercn/`. Add `-g` to install it for every project on your machine. The skill adds the engine to your project the first time your agent builds a dither. ## Or add the engine and the skill with shadcn [#or-add-the-engine-and-the-skill-with-shadcn] ### Run the add command [#run-the-add-command] ```bash npx shadcn@latest add https://dithercn.dev/r/dithercn-skill.json ``` This writes: * `lib/dither/`, the engine (`dither-core`) and its export functions (`dither-export`). * `.agents/skills/dithercn/` and `.claude/skills/dithercn/`, the agent skill. Claude Code reads `.claude/skills`; Codex, Cursor and other agents that follow the [Agent Skills](https://agentskills.io) layout read `.agents/skills`. * The `--dither-*` CSS variables, in the CSS file named in your `components.json`. Commit the skill folders with the rest of your code, so everyone on the project, and every agent, gets the same skill. ### Check your CSS [#check-your-css] The CLI adds the variables for light and dark mode, and colour utilities to the `@theme inline` block: ```css @theme inline { --color-dither-ink: var(--dither-ink); --color-dither-paper: var(--dither-paper); /* and one per named colour: --color-dither-blue, --color-dither-green, … */ } @layer base { :root { --dither-ink: var(--primary); --dither-paper: var(--background); --dither-cell: 3px; } .dark { --dither-ink: var(--primary); --dither-paper: var(--background); --dither-cell: 3px; } } ``` Because the variables point at your theme tokens, there is nothing extra to set for dark mode. [Theming](/docs/theming) lists every variable. Then ask your agent for a dither. The [introduction](/docs#ask-your-agent-for-a-dither) has example requests and explains what the agent does with them. ## What's in the skill [#whats-in-the-skill] | File | What it covers | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SKILL.md` | The four steps the agent follows, and the rules for every dither | | `references/engine-api.md` | Every engine export: `DitherShader`, `FillPattern`, `fillStyle`, colours, motion hooks, `ditherImage` and export | | `references/fields.md` | How to write a GLSL field, with building blocks for gradients, noise, contour lines, rings, arcs and shading | | `references/recipes.md` | Complete components to adapt: a shader background, a hero that clears its headline, an SVG chart fill, a progress bar, a dithered photo, a CSS-only band | | `references/landing-pages.md` | Designing the page around the dither: start from the product, one memorable hero, true copy, the tells to avoid | | `references/placement.md` | Putting a dither on a landing page or dashboard: layering, sizing, server and client components | | `references/theme.md` | Tokens, named colours, palettes, light and dark mode | | `references/quality.md` | The checks: looks, contrast, reduced motion, accessibility, performance | | `references/examples.md` | Every example component, with its install command and docs link | The skill matches the engine it installs with and points your agent at the files in your project, so it never has to guess at an API. ## Install the skill for every project [#install-the-skill-for-every-project] `shadcn add` writes the skill into the current project only. To make it available in every project on your machine, run the skills CLI with `-g`. Without the CLI, download the files into your user skills folder: ```bash SKILL=https://dithercn.dev/.well-known/agent-skills/dithercn for dir in ~/.claude/skills/dithercn ~/.agents/skills/dithercn; do mkdir -p "$dir/references" curl -fsSL "$SKILL/SKILL.md" -o "$dir/SKILL.md" for ref in engine-api examples fields landing-pages placement quality recipes theme; do curl -fsSL "$SKILL/references/$ref.md" -o "$dir/references/$ref.md" done done ``` The skill tells the agent to install the engine into a project the first time it needs it. ## Install the engine or one example [#install-the-engine-or-one-example] Without the skill, install the engine on its own: ```bash npx shadcn@latest add https://dithercn.dev/r/dither-core.json ``` Or install any example. Each one brings the engine with it on its first install: ```bash npx shadcn@latest add https://dithercn.dev/r/dither-gradient.json ``` ## Update the engine [#update-the-engine] The engine is plain source in your project, so it doesn't update on its own. Newer docs can use features your copy lacks: if TypeScript says a prop such as `colorField` doesn't exist on `DitherShader`, delete `lib/dither` and add the engine again: ```bash rm -rf lib/dither npx shadcn@latest add https://dithercn.dev/r/dither-core.json https://dithercn.dev/r/dither-export.json ``` If the CLI asks whether to overwrite `lib/utils.ts`, answer no. Avoid `--overwrite`, which replaces that file too. If you edited any file in `lib/dither`, copy your changes out first. ## Use a namespace [#use-a-namespace] To install by name instead of by URL, add dithercn to the `registries` field of your `components.json`: ```json title="components.json" { "registries": { "@dithercn": "https://dithercn.dev/r/{name}.json" } } ``` Then install with the `@dithercn` prefix: ```bash npx shadcn@latest add @dithercn/dithercn-skill ``` See the shadcn docs on [namespaced registries](https://ui.shadcn.com/docs/registry/namespace) for more options, including authentication. ## Other ways in for agents [#other-ways-in-for-agents] The skill is the main way in. The site also publishes plain-text surfaces for agents that browse: * [`/llms.txt`](/llms.txt) lists every docs page with a one-line description and a Markdown link, and [`/llms-full.txt`](/llms-full.txt) is the whole documentation as one file. * Add `.md` to any docs URL for its Markdown, for example [`/docs/installation.md`](/docs/installation.md), or send an `Accept: text/markdown` header. Every page also has a **Copy page** menu. * The registry index is at [`/r/registry.json`](/r/registry.json), and each item at `/r/.json`. The [shadcn MCP server](https://ui.shadcn.com/docs/mcp) can browse and install from it once you have [added the `@dithercn` namespace](#use-a-namespace). * [`/.well-known/api-catalog`](/.well-known/api-catalog) links every machine-readable surface (RFC 9727), [`/openapi.json`](/openapi.json) describes the read-only endpoints, and [`/api/status`](/api/status) returns `{"status":"ok"}`. The home page sends these as `Link` headers too. ## Next steps [#next-steps] * [Ask your agent for a dither](/docs#ask-your-agent-for-a-dither) with the skill installed. * Browse the [catalog](/docs/examples) for a component to install as it is. * [Write your own dither](/docs/engine/write-your-own) by hand, step by step. # Theming How dithercn reads your shadcn theme, and how to override colours and cell size. > 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). Every dither reads a few CSS variables, and the main two point at your existing shadcn tokens by default. Change them and every dither on the page follows, including the ones your agent writes. ## The variables [#the-variables] | Variable | Default | What it controls | | ----------------------- | --------------------- | --------------------------------------------------------------- | | `--dither-ink` | `var(--primary)` | The colour of the dots | | `--dither-paper` | `var(--background)` | The colour behind the dots | | `--dither-cell` | `3px` | The size of one dither cell in CSS pixels | | `--dither-bloom` | `1` dark, `0.6` light | How strong glows are | | `--dither-color-` | tuned per theme | The named colours: blue, purple, green, pink, orange, red, grey | [`dither-tokens`](/docs/installation) writes these into both `:root` and `.dark`. Components and the engine accept the names `"ink"`, `"blue"`, `"green"` and so on wherever they take a colour, and Tailwind gets matching utilities such as `bg-dither-blue`. When the visitor switches to dark mode, `--primary` and `--background` change, and every dither changes with them. ## Override for one section [#override-for-one-section] Variables cascade, so you can retint a single part of the page by setting them on a wrapper: ```css .pricing-hero { --dither-ink: var(--chart-2); --dither-cell: 4px; } ``` Anything inside `.pricing-hero` uses the new ink and a coarser grain. The rest of the page is unchanged. ## Pick colours that still read [#pick-colours-that-still-read] A dither only reads when ink and paper differ enough. As a rule, keep the same contrast you would want for body text, about 4.5:1, between `--dither-ink` and `--dither-paper`. Low-contrast pairs look fine as a quiet background, but they lose detail in charts and images. ## Multi-step palettes [#multi-step-palettes] Two-tone dithers cover most backgrounds and interface pieces. Components that need more steps take a `palette` prop: a list of colours from paper to ink, such as your `--chart-1` to `--chart-5` tokens. [Palettes](/docs/engine/palettes) shows how a palette ramp is dithered. # Dither engine How dither-core, the engine behind every dithercn dither, turns a tone into dots: threshold matrices, levels, error diffusion, and the three ways to draw. > 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). `dither-core` is the dither engine. It installs into `lib/dither` as plain source, with the [agent skill](/docs/installation) or the first time you add an example, and every dither on this site is built on it. Import it to build your own: ```tsx import { DitherShader, ditherMaskStyle, ditherImage } from "@/lib/dither"; ``` It draws dithers three ways. Pick by what you're making: | You want | Use | Drawn with | | ---------------------------------- | ---------------------------------- | ----------------- | | An animated or full-bleed surface | `DitherShader` | WebGL | | A static fill for a shape or block | `DitherPattern`, `ditherMaskStyle` | SVG or a CSS mask | | A dithered photo or logo | `ditherImage` | The CPU, once | ## From tone to dots [#from-tone-to-dots] A dither starts from a tone between 0 and 1 for each cell: 0 is paper, 1 is ink. A threshold matrix gives every cell in a small repeating tile its own threshold. Where the tone is above the cell's threshold, the cell is inked. A tone of 0.25 inks a quarter of the cells, spread evenly across the tile. The matrix decides how those cells are spread. Each one below draws the same left-to-right ramp: ```tsx title="examples/concept-matrices.tsx" import { DitherShader } from "@/lib/dither"; import type { DitherMatrix } from "@/lib/dither"; // A left-to-right ramp: tone equals the horizontal position. const RAMP = `float field(vec2 uv, vec2 res, float t) { return uv.x; }`; const MATRICES: { matrix: DitherMatrix; note: string }[] = [ { matrix: "bayer2", note: "5 tones, coarse crosshatch" }, { matrix: "bayer4", note: "17 tones, the default" }, { matrix: "bayer8", note: "65 tones, finer steps" }, { matrix: "blue-noise", note: "257 tones, no visible grid" }, ]; export default function ConceptMatrices() { return (
{MATRICES.map(({ matrix, note }) => (
{matrix} {note}
))}
); } ``` * `bayer2`, `bayer4` and `bayer8` are ordered Bayer matrices of 2, 4 and 8 cells square. Bigger tiles draw more distinct tones, with a regular crosshatch that reads as deliberate and printed. * `blue-noise` is a 64×64 tile with no visible grid. It looks closest to film grain, and suits large soft surfaces where a crosshatch would distract. `bayer4` is the default everywhere. Every matrix is ordered, so the same tone always gives the same dots. That keeps animated dithers calm: a cell only changes when its tone crosses its threshold. ## Levels [#levels] Before dithering, the tone snaps to a fixed number of `levels`. By default that's every tone the matrix can draw, 17 for `bayer4`. Fewer levels give visible bands, a posterised look that works well for bold backgrounds: ```tsx title="examples/concept-levels.tsx" "use client"; import { useId, useState } from "react"; import { DitherShader } from "@/lib/dither"; const RAMP = `float field(vec2 uv, vec2 res, float t) { return uv.x; }`; export default function ConceptLevels() { const [levels, setLevels] = useState(5); const id = useId(); return (
setLevels(Number(event.target.value))} className="accent-primary h-1 flex-1 cursor-pointer" /> {levels}
); } ``` ## Error diffusion for images [#error-diffusion-for-images] Photos and logos look best with error diffusion. Each pixel is rounded to the nearest colour, and the rounding error is passed on to the pixels not yet visited. `ditherImage` supports `floyd-steinberg`, `atkinson` and `sierra-lite` alongside the ordered matrices: ```tsx title="examples/concept-diffusion.tsx" "use client"; import { useEffect, useRef } from "react"; import { ditherImage, useDitherColors } from "@/lib/dither"; import type { DitherMethod, Rgba } from "@/lib/dither"; const SIZE = 96; const METHODS: DitherMethod[] = [ "bayer4", "blue-noise", "floyd-steinberg", "atkinson", ]; // A test picture: a lit sphere on a soft vertical gradient. const testImage = () => { const data = new Uint8ClampedArray(SIZE * SIZE * 4); for (let y = 0; y < SIZE; y += 1) { for (let x = 0; x < SIZE; x += 1) { const dx = (x - SIZE * 0.55) / (SIZE * 0.3); const dy = (y - SIZE * 0.5) / (SIZE * 0.3); const inside = dx * dx + dy * dy; const sky = 0.15 + 0.35 * (y / SIZE); const light = Math.max(0, 1 - Math.hypot(dx + 0.45, dy + 0.45) / 1.6); const tone = inside < 1 ? 0.1 + 0.9 * light : sky; const offset = (y * SIZE + x) * 4; data.fill(Math.round(tone * 255), offset, offset + 3); data[offset + 3] = 255; } } return { data, height: SIZE, width: SIZE }; }; const DitheredCanvas = ({ method, paper, ink, }: { method: DitherMethod; paper: Rgba; ink: Rgba; }) => { const ref = useRef(null); useEffect(() => { const context = ref.current?.getContext("2d"); if (!context) { return; } // Dark pixels become paper and light ones ink, so the picture reads // the same on a black or a white page. const result = ditherImage(testImage(), { method, palette: [ [0, 0, 0], [1, 1, 1], ], }); const { data } = result; for (let index = 0; index < data.length; index += 4) { const color = (data[index] ?? 0) > 127 ? ink : paper; data[index] = color[0] * 255; data[index + 1] = color[1] * 255; data[index + 2] = color[2] * 255; } context.putImageData(result, 0, 0); }, [method, paper, ink]); return ( ); }; export default function ConceptDiffusion() { const root = useRef(null); const colors = useDitherColors(root); return (
{METHODS.map((method) => (
{colors ? ( ) : (
)}
{method}
))}
); } ``` `ditherImage` never changes its input. It returns a new `ImageData` you can draw with `putImageData`, and it has no DOM dependency beyond `ImageData`, so it runs in a Web Worker too. Error diffusion depends on every earlier pixel, so a small change to the input can move dots everywhere. Keep it for still images and use a matrix for anything that moves. ```ts const dithered = ditherImage(context.getImageData(0, 0, width, height), { method: "atkinson", palette: [ [0.05, 0.05, 0.1], [0.55, 0.6, 1], ], }); context.putImageData(dithered, 0, 0); ``` ## Writing a field [#writing-a-field] `DitherShader` runs a GLSL function you write, called the field. [Write your own dither](/docs/engine/write-your-own) builds one step by step; this is the short version. It receives the cell's position and returns its tone; the core does the rest: resizing, quantising, the palette, motion and cleanup. ```tsx const FIELD = ` float field(vec2 uv, vec2 res, float t) { // uv: cell centre in [0, 1], y up. res: size in cells. t: seconds. float glow = 1.0 - distance(uv, glowCentre); return clamp(glow + 0.2 * (fbm(uv * 3.0 + t * 0.05) - 0.5), 0.0, 1.0); } `; ; ``` Values in `uniforms` are declared for you: a number becomes a `float`, and a tuple of 2, 3 or 4 numbers a `vec2`, `vec3` or `vec4`. Changing a value only updates the uniform; changing the field's source recompiles it. The core gives every field three helpers: `hash21(vec2)` for stable per-cell randomness in \[0, 1), `snoise(vec2)` for simplex noise in \[-1, 1], and `fbm(vec2)` for four octaves of it in \[0, 1]. Names starting with `u_` belong to the core's own uniforms, so your uniforms can't use them. A second, optional function, `colorField`, sets the colour of the dots independently of their density, for rainbows and a colour per shape. [Palettes](/docs/engine/palettes#any-number-of-hues) shows it, with the `spectrum` and `paletteColor` helpers it can use. ## Static fills [#static-fills] Charts and small surfaces don't need a canvas. `DitherPattern` is an SVG `` you reference from any shape, and `ditherMaskStyle` masks any element to one tone: ```tsx
``` Both render on the server, follow the theme through CSS variables, and cost nothing after the first paint. # Write your own dither Build a dithered background from scratch on DitherShader: a tone, a shape, then motion and props. > 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 guide builds a dithered ripple background in three steps. You write one GLSL function, the field, and `DitherShader` does everything else: the dots, the colours, resizing, motion and reduced motion. The same steps make any background you can describe with a little math. Your agent follows them too, through the [dithercn skill](/docs/installation). You need the engine installed (see [Installation](/docs/installation)) and a section with a size to put the dither in. ## Have your agent write it [#have-your-agent-write-it] Your agent can follow this page. Fill in the two blanks and paste this in: ```text Write a dithered background for that looks like . Follow https://dithercn.dev/docs/engine/write-your-own.md and the dithercn skill's references/fields.md. Build it on DitherShader from lib/dither and colour it from the theme. Check it in a browser in light and dark mode and with reduced motion. ``` If the engine isn't installed yet, start with the [quick start prompt](/docs/installation#quick-start-with-your-agent) instead. The rest of this page is the same build, step by step. ## 1. Return a tone [#1-return-a-tone] The field runs once for every cell and returns a tone from 0 to 1: 0 is paper, 1 is ink. The engine turns that tone into dots, so a field that returns the height draws a gradient from bottom to top: ```tsx "use client"; import { DitherShader } from "@/lib/dither"; const FIELD = ` float field(vec2 uv, vec2 res, float t) { return uv.y; } `; export function Ripples() { return ( ); } ``` ```tsx title="examples/engine-field-tone.tsx" "use client"; import { DitherShader } from "@/lib/dither"; // Step 1: the tone is just the height. 0 at the bottom (paper), 1 at the top (ink). const FIELD = ` float field(vec2 uv, vec2 res, float t) { return uv.y; } `; export default function EngineFieldTone() { return ( ); } ``` `uv` is the cell's position from 0 to 1 on both axes, with y pointing up. `res` is the size of the canvas in cells, and `t` is time in seconds. Return smooth values and let the engine do the dithering. ## 2. Draw a shape [#2-draw-a-shape] `uv` stretches with the box, so a circle measured in `uv` comes out as an oval. Multiply x by the aspect ratio first, and distances are true. Then compute anything that has a distance in it, here rings around a point that fade as they spread: ```glsl float field(vec2 uv, vec2 res, float t) { float aspect = res.x / res.y; vec2 p = vec2(uv.x * aspect, uv.y); vec2 drop = vec2(0.5 * aspect, 0.45); float d = length(p - drop); float rings = 0.5 + 0.5 * cos(d * 48.0); float fade = exp(-d * 3.0); return rings * fade; } ``` ```tsx title="examples/engine-field-rings.tsx" "use client"; import { DitherShader } from "@/lib/dither"; // Step 2: square units, then rings around a point that fade with distance. const FIELD = ` float field(vec2 uv, vec2 res, float t) { float aspect = res.x / res.y; vec2 p = vec2(uv.x * aspect, uv.y); vec2 drop = vec2(0.5 * aspect, 0.45); float d = length(p - drop); float rings = 0.5 + 0.5 * cos(d * 48.0); float fade = exp(-d * 3.0); return rings * fade; } `; export default function EngineFieldRings() { return ( ); } ``` `cos` turns distance into repeating bands, and `exp` fades them out. Every tone between 0 and 1 becomes a different dot density, which is why the rings look soft rather than drawn. ## 3. Add motion and props [#3-add-motion-and-props] Three changes finish it. Noise bends the rings so they look like water, `t` moves them outwards, and uniforms turn the fixed numbers into props: ```tsx "use client"; import { DitherShader } from "@/lib/dither"; import type { DitherShaderProps } from "@/lib/dither"; const FIELD = ` float field(vec2 uv, vec2 res, float t) { float aspect = res.x / res.y; vec2 p = vec2(uv.x * aspect, uv.y); vec2 drop = vec2(rippleDrop.x * aspect, 1.0 - rippleDrop.y); float wobble = fbm(p * 2.2 + vec2(t * 0.03, 0.0)) - 0.5; float d = length(p - drop) + wobble * 0.08; float rings = 0.5 + 0.5 * cos(d * rippleRings - t * 0.9); float fade = exp(-d * 2.6); return rippleStrength * rings * fade; } `; export type RipplesProps = Omit & { /** Where the drop falls, from the top left, 0 to 1. */ drop?: readonly [number, number]; /** How tightly the rings are packed. */ rings?: number; /** Peak tone, 0 to 1. */ strength?: number; }; export const Ripples = ({ drop = [0.5, 0.42], rings = 46, strength = 0.85, ...props }: RipplesProps) => ( ); ``` ```tsx title="examples/engine-field-ripples.tsx" "use client"; import { DitherShader } from "@/lib/dither"; import type { DitherShaderProps } from "@/lib/dither"; // Step 3: noise bends the rings, time moves them outwards, and uniforms // turn the numbers into props. const FIELD = ` float field(vec2 uv, vec2 res, float t) { float aspect = res.x / res.y; vec2 p = vec2(uv.x * aspect, uv.y); vec2 drop = vec2(rippleDrop.x * aspect, 1.0 - rippleDrop.y); float wobble = fbm(p * 2.2 + vec2(t * 0.03, 0.0)) - 0.5; float d = length(p - drop) + wobble * 0.08; float rings = 0.5 + 0.5 * cos(d * rippleRings - t * 0.9); float fade = exp(-d * 2.6); return rippleStrength * rings * fade; } `; type RipplesProps = Omit & { /** Where the drop falls, from the top left, 0 to 1. */ drop?: readonly [number, number]; /** How tightly the rings are packed. */ rings?: number; /** Peak tone, 0 to 1. */ strength?: number; }; const Ripples = ({ drop = [0.5, 0.42], rings = 46, strength = 0.85, ...props }: RipplesProps) => ( ); export default function EngineFieldRipples() { return (

Ripples, in eight lines of GLSL.

); } ``` * `fbm` is one of three helpers every field gets, with `snoise` and `hash21`. It returns soft, cloudy noise from 0 to 1. * Each key in `uniforms` is declared for you: a number becomes a `float`, a pair a `vec2`. Changing a prop updates the uniform without recompiling. Prefix the names (`ripple…`) so they never clash with another field. * `stillTime={4}` is the moment shown when the visitor prefers reduced motion. Pick one that looks finished. * The rest of `DitherShaderProps` passes through, so `palette`, `matrix`, `cell` and `speed` work on `Ripples` as they do on every example. ## Put it on the page [#put-it-on-the-page] ```tsx
…
``` The shader fills its positioned parent and ignores the pointer, so your content sits in a sibling on top. Keep the tone low where text sits; here `strength` caps it at 0.85, and the rings fade before they reach the edges. ## Where to go next [#where-to-go-next] * [Dither engine](/docs/engine/dither-engine) covers matrices and levels, and the static fills for charts and blocks. * [Palettes](/docs/engine/palettes) covers colour, including a `colorField` that colours the dots on its own, for rainbows and a hue per shape. [Pixel scale](/docs/engine/pixel-scale) and [Motion](/docs/engine/motion) cover the other shared props. * The examples in the [catalog](/docs/examples) are all built this way. Open any background's source for a field to start from. # Palettes How dithers take their colours from your theme, and how to use more than two. > 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). A palette is the list of colours a dither can use, ordered from paper (tone 0) to ink (tone 1). By default it's two colours, `--dither-paper` and `--dither-ink`, which [`dither-tokens`](/docs/theming) points at your `--background` and `--primary`. ```tsx title="examples/concept-palettes.tsx" import { DitherPattern, DitherShader } from "@/lib/dither"; const RAMP = `float field(vec2 uv, vec2 res, float t) { return uv.x; }`; const BARS = [ { height: 46, level: 0.25 }, { height: 72, level: 0.5 }, { height: 58, level: 0.75 }, { height: 88, level: 1 }, ] as const; export default function ConceptPalettes() { return (
Two colours:{" "} --dither-paper to{" "} --dither-ink
Four colours:{" "} --background then{" "} --chart-3 to{" "} --chart-1
{BARS.map(({ level }) => ( ))} {BARS.map(({ height, level }, index) => ( ))}
DitherPattern fills
); } ``` ## More than two colours [#more-than-two-colours] Components that draw with `DitherShader` take a `palette` prop with two or more colours. The tone is spread across the list, and each cell is dithered between the two neighbouring colours. With four colours, a tone of 0.5 sits halfway between the second and third: ```tsx ``` Each entry is a CSS variable name such as `"--chart-1"`, or any CSS colour: `"var(--chart-1)"`, `"oklch(0.72 0.15 272)"`, `"#7c8cff"`, even `"transparent"` to let the page show through the paper. The type is `DitherPalette`, a tuple of at least two `DitherColor` strings, so a one-colour palette fails to compile. Order matters more than hue. Put colours in order of how much they stand out against the paper, or the ramp will jump back and forth. ## Any number of hues [#any-number-of-hues] A field returns one tone per cell, and the palette turns that tone into a colour. So with a palette of several hues, the tone decides both how dense the dots are and which colour they take. That suits ramps, such as a glow that runs from deep blue to white, but it can't draw a red line and a blue line at the same strength, or a spectrum that runs along a line. For that, add a `colorField`: a second function that returns the colour of the dots in each cell. The field still decides where the dots go, and cells without a dot are paper, `palette[0]`: ```tsx title="examples/concept-hues.tsx" import { DitherShader } from "@/lib/dither"; import type { DitherShaderProps } from "@/lib/dither"; // Shared by both functions: the nearest line to this cell, as // (distance in line widths, line index). const LINES = ` vec2 spectrumNearest(vec2 uv, vec2 res, float t) { float x = uv.x * res.x / res.y; vec2 best = vec2(1e4, 0.0); for (int i = 0; i < 9; i++) { if (float(i) >= spectrumLines) break; float k = float(i); float base = 0.18 + 0.64 * k / max(spectrumLines - 1.0, 1.0); float y = base + 0.035 * sin(x * 2.4 + t * 0.5 + k * 0.9) + 0.015 * sin(x * 5.3 - t * 0.35 + k * 1.7); float d = abs(uv.y - y) * res.y / spectrumWidth; if (d < best.x) best = vec2(d, k); } return best; } `; // Where the dots go: a thin core and a soft glow around each line. const FIELD = `${LINES} float field(vec2 uv, vec2 res, float t) { float d = spectrumNearest(uv, res, t).x; return exp(-d * d) + 0.35 * exp(-d * 0.6); } `; // What colour they are: a rainbow that runs along each line and drifts, // shifted a little per line. Mixing in the theme's ink keeps it readable // on white as well as black. const COLOR_FIELD = ` vec3 colorField(vec2 uv, vec2 res, float t) { float k = spectrumNearest(uv, res, t).y; vec3 hue = spectrum(uv.x * 0.75 + k * 0.06 - t * 0.03); return mix(hue, paletteColor(0.0).rgb, 0.2); } `; type SpectrumLinesProps = Omit< DitherShaderProps, "field" | "colorField" | "uniforms" > & { /** How many lines, from 1 to 9. */ lines?: number; /** Line thickness in cells; the glow spreads a few times wider. */ width?: number; }; const SpectrumLines = ({ lines = 7, width = 1.2, palette = ["--background", "--foreground"], ...props }: SpectrumLinesProps) => ( ); export default function ConceptHues() { return ; } ``` ```tsx const COLOR_FIELD = ` vec3 colorField(vec2 uv, vec2 res, float t) { return spectrum(uv.x * 0.75 - t * 0.03); } `; ; ``` It takes the same arguments and uniforms as the field, and returns `vec3` RGB or `vec4` RGBA. If TypeScript says `colorField` doesn't exist, your copy of the engine is older than it: [update the engine](/docs/installation#update-the-engine). Two helpers come with it: * `spectrum(h)` is a saturated rainbow: 0 is red, then yellow, green, cyan, blue and magenta, and red again at 1. It wraps, so `h` can keep growing with time. * `paletteColor(x)` reads the palette's ink colours, every entry after the paper, as a smooth ramp from 0 to 1. Use it to colour by position in your theme colours: `return paletteColor(uv.x).rgb;` with `palette={["--background", "--chart-1", "--chart-2", "--chart-3"]}`. Soft tones still dither, so a coloured glow fades out in sparse dots of its colour. To colour several shapes differently, work out which shape a cell is in with a helper. Both strings compile into one shader: define the helper in the field string, or prepend the same helpers to both (the engine keeps one copy), and call it from both functions; the demo above finds the nearest line that way and shifts each line's hue. `spectrum` is fully saturated, so check light mode: yellow and cyan nearly vanish on white. The demo mixes a fifth of the theme's foreground into every hue, `mix(spectrum(h), paletteColor(0.0).rgb, 0.2)`, which keeps it readable in both modes. If the colour has to come from outside the shader, such as an image or a CSS gradient, put that layer under the shader and use `palette={["--background", "transparent"]}`: tone 0 paints the background and tone 1 shows the layer. ## Following the theme [#following-the-theme] Colours are resolved in the browser, in the context of the component, so a variable you override on a wrapper applies to everything inside it. Formats the GPU can't read, such as `oklch()`, are converted by painting them once into a 1×1 canvas. When the class or style on `` changes, as it does when next-themes switches between light and dark, every dither re-reads its colours and redraws. Nothing else needs to happen. For your own canvas work, `useDitherColors` does the same and returns the colours as `[r, g, b, a]` in \[0, 1]: ```tsx const ref = useRef(null); const colors = useDitherColors(ref); // [ink, paper], or null before mount ``` Pass a list of names as the second argument to read other variables, such as `useDitherColors(ref, ["--chart-1", "--chart-2"])`. ## Static fills [#static-fills] `DitherPattern` takes a `color` and an optional `background`, and `ditherMaskStyle` masks whatever background colour the element already has. Both accept CSS variables, so they follow the theme without JavaScript. ## Contrast [#contrast] A dither reads only when ink and paper differ enough. For charts and anything that carries information, aim for the contrast you would want for body text, about 4.5:1. Quiet backgrounds can go lower, since nobody needs to read them. The defaults, your primary on your background, usually clear that bar in both modes. # Pixel scale How big one dither cell is, how it stays crisp on every screen, and how to change it. > 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). Every dither is drawn on a grid of square cells, and each cell is either ink or paper. The size of one cell sets the grain: small cells read as texture, large ones as pixel art. ```tsx title="examples/concept-cell.tsx" "use client"; import { useId, useState } from "react"; import { DitherShader } from "@/lib/dither"; // A soft glow from the lower left corner. const GLOW = ` float field(vec2 uv, vec2 res, float t) { float aspect = res.x / res.y; vec2 p = vec2(uv.x * aspect, uv.y); return clamp(1.15 - length(p) * 0.85, 0.0, 1.0); } `; export default function ConceptCell() { const [cell, setCell] = useState(3); const id = useId(); return (
setCell(Number(event.target.value))} className="accent-primary h-1 flex-1 cursor-pointer" /> {cell}px
); } ``` ## Setting the size [#setting-the-size] The size comes from the `--dither-cell` CSS variable, 3px by default. Change it for the whole site in your CSS, or for one section by setting it on a wrapper: ```css .pricing-hero { --dither-cell: 5px; } ``` To size one component directly, pass `cell` in CSS pixels. It wins over the variable: ```tsx ``` ## Crisp on every screen [#crisp-on-every-screen] `DitherShader` renders one canvas pixel per cell and scales the canvas up with `image-rendering: pixelated`, so a large hero costs about as much as a thumbnail. A 1440×700 hero at 3px cells is 480×234 pixels of shading. To keep every cell the same size, the cell is rounded to whole device pixels. On a 2× screen a 3px cell is exactly 6 device pixels. At 1.5× it would be 4.5, so it becomes 5 device pixels, 3.33 CSS pixels. Without this rounding, neighbouring cells would alternate between 4 and 5 pixels and the pattern would shimmer. The canvas covers its box to the next whole cell and the overflow is clipped, so the grid always starts at the top left corner. ## Static fills [#static-fills] `ditherMaskStyle` tiles its mask at `--dither-cell` too. Its `cell` option takes CSS pixels or any CSS length: ```tsx
``` `DitherPattern` lives inside an SVG, so its `cell` is in SVG user units: CSS pixels when the SVG is drawn at its natural size. If the SVG scales with a `viewBox`, the cells scale with it. ## Choosing a size [#choosing-a-size] * 2 to 3px reads as fine grain. It's the right default for large backgrounds behind text. * 4 to 6px is clearly a dither from across the room, good for heroes that are the main visual. * 8px and up is pixel art. Pair it with fewer `levels` for a bold, posterised look. # Motion How animated dithers run, pause and hold still, including under reduced motion. > 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). Animated dithers in dithercn are slow on purpose. A background should drift, not perform. Every animated component follows the same rules, and they come from `dither-core`, so you get them for free when you build your own. ```tsx title="examples/concept-motion.tsx" "use client"; import { useEffect, useRef, useState } from "react"; import { DitherShader } from "@/lib/dither"; // Slow drifting noise: two fbm layers moving against each other. const DRIFT = ` float field(vec2 uv, vec2 res, float t) { float aspect = res.x / res.y; vec2 p = vec2(uv.x * aspect, uv.y) * 1.6; float n = fbm(p + vec2(t * 0.06, t * 0.02)); float m = fbm(p * 1.7 - vec2(t * 0.03, 0.0) + n); return smoothstep(0.25, 0.85, m) * (0.35 + 0.65 * uv.y); } `; /** Mirrors the shader's data-state attribute so the demo can show it. */ const useDitherState = () => { const ref = useRef(null); const [state, setState] = useState("fallback"); useEffect(() => { const el = ref.current; if (!el) { return; } const read = () => setState(el.dataset.state ?? "fallback"); read(); const observer = new MutationObserver(read); observer.observe(el, { attributeFilter: ["data-state"], attributes: true }); return () => observer.disconnect(); }, []); return { ref, state }; }; export default function ConceptMotion() { const [animate, setAnimate] = useState(true); const { ref, state } = useDitherState(); return (
data-state {state}
); } ``` ## When a dither moves [#when-a-dither-moves] A `DitherShader` animates only while someone can see it. Its `data-state` attribute says what it is doing: | `data-state` | Meaning | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | `running` | Drawing up to 30 frames a second. | | `paused` | Scrolled out of view or in a hidden tab. Time stops, so it resumes where it left off. | | `static` | Motion is off. Reduced motion draws `stillTime`. `animate={false}` holds the current frame, or draws `stillTime` if the dither never moved. | | `fallback` | Nothing drawn yet, WebGL is unavailable, or the shader is far offscreen and has released its context. The `fallback` content shows instead. | Time only advances while running, and a long frame (a tab switch, a debugger) counts as at most a tenth of a second, so the motion never jumps. ## Reduced motion [#reduced-motion] When the visitor turns on reduced motion in their system settings, every dither draws one still frame and stops. `stillTime` picks which moment of the animation that frame shows, so you can choose a good one: ```tsx ``` The switch is live: turning the setting on or off while the page is open takes effect at once. Interface pieces follow the same rule. Their transitions become instant, and anything that loops holds still. ## Speed and frame rate [#speed-and-frame-rate] `speed` scales time, and changing it never jumps the motion, because each frame adds its own slice of time. `fps` caps the frame rate. The default of 30 keeps slow drifts smooth at half the cost of 60. ## Your own loops [#your-own-loops] `useFrameLoop` is the loop `DitherShader` uses. Give it a callback and an element, and it calls you once per frame while that element is on screen: ```tsx const ref = useRef(null); const state = useFrameLoop( (time, delta) => { // draw the frame for `time` seconds }, { target: ref, fps: 30, stillTime: 0 } ); ``` Under reduced motion or with `enabled: false`, it calls the callback once with `stillTime` and a `delta` of 0. It returns `"running"`, `"paused"` or `"static"`, and only re-renders when that changes. `usePrefersReducedMotion`, `useInView` and `useDocumentVisible` are exported too. ## Easing and timing [#easing-and-timing] For transitions in interface pieces, `MOTION` holds the shared values: ```ts MOTION.ease; // "cubic-bezier(0.23, 1, 0.32, 1)", a strong ease-out MOTION.fast; // 150ms, for presses and colour changes MOTION.base; // 240ms, for small reveals MOTION.slow; // 600ms, for large surfaces fading in ``` Hover effects use Tailwind's `hover:` variant, which only applies on devices that can hover, so a tap on a phone never leaves an element stuck in its hover state. # Performance What a dither costs to draw, and how dither-core keeps a page of them cheap. > 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). Dithers look heavy and draw light. Each technique has a predictable cost, and the expensive parts happen once. ## What each technique costs [#what-each-technique-costs] | Technique | Cost | | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `DitherShader` | One draw call per frame, at one pixel per cell, capped at 30 fps. No allocations per frame. | | `DitherPattern`, `ditherMaskStyle` | One small SVG, built once per level and matrix and cached. No JavaScript after the first paint. | | `ditherImage` | One pass over the image, with no DOM. Error diffusion visits every pixel in order, so run large images in a Web Worker. | A full-width hero at 3px cells shades about 110,000 pixels a frame. A 1080p video frame is two million. ## Pausing [#pausing] A shader stops drawing when it scrolls out of view or the tab is hidden, and holds a still frame under reduced motion. Its uniforms can still change while paused, and it redraws once when they do. ## The WebGL context budget [#the-webgl-context-budget] Browsers cap how many WebGL contexts a page can hold. Chrome drops the oldest past 16, and phones can run out of GPU memory sooner. `dither-core` shares a budget between every `DitherShader` on the page: * A shader holds a context only while it is within 200px of the viewport. Further away, it releases the context and shows its `fallback`. * At most 8 contexts are live at once (`MAX_LIVE_CONTEXTS`). When a ninth shader comes on screen, it takes the context of one just outside the viewport. It never takes one from a shader that is still on screen, so scrolling doesn't make visible dithers flicker; it waits for the next free context instead. * When a shader scrolls back, it gets a fresh context and fades its canvas in over the fallback. So a long docs page with a dozen backgrounds keeps at most 8 contexts, and normally only the two or three on screen. Give each shader a `fallback` that resembles it, such as the CSS gradient `DitherGradient` uses, and a released one is hard to spot. ## Galleries and thumbnails [#galleries-and-thumbnails] A grid of 30 live backgrounds would spend its budget on the first 8. For catalog pages and galleries, show a static image or a `ditherMaskStyle` composition instead, and mount the live shader on the detail page. The docs on this site do the same: each preview mounts its demo only when it nears the viewport. ## Images [#images] `ditherImage` is synchronous. A 512×512 image takes 10 to 30 ms; a 4K photo, 32 times the pixels, can block the main thread for half a second or more. Dither at the size you display, after scaling the image down to one pixel per cell, and move larger jobs to a Web Worker. The function is pure and has no DOM dependency beyond `ImageData`, so the same import works there. ## Blue noise in masks [#blue-noise-in-masks] The blue-noise tile is 64×64 cells, against 4×4 for `bayer4`. Its SVG mask is correspondingly larger, 15 to 20 KB per level against about 100 bytes. Use it in shaders freely, where it is a single texture. For CSS masks, prefer a Bayer matrix unless you need only a level or two. # Export Turn any dither into a PNG, a compact SVG or a CSS rule from your own code, with dither-export. > 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). `dither-export` turns a dither into files. It takes the `ImageData` from `ditherImage`, one pixel per cell, or a grid of palette indices you worked out yourself, and returns a PNG, an SVG or a CSS rule. Use it to bake a dither into a static asset, to ship a background with no JavaScript, or to give your users a download button. ## Which format [#which-format] | Format | Use it for | | ------ | -------------------------------------------------------- | | PNG | Photos and diffusion dithers. Pixel-exact at any scale. | | SVG | Ordered dithers and flat art. Sharp at any size. | | CSS | Backgrounds in a stylesheet: the whole image, or a tile. | ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/dither-export.json ``` ## Usage [#usage] ```tsx import { ditherImage } from "@/lib/dither"; import { download, toCSS, toPNG, toSVG } from "@/lib/dither/export"; const dither = ditherImage(pixels, { method: "atkinson", palette: [ [0.04, 0.04, 0.05], [0.95, 0.94, 0.91], ], }); // A full-size PNG: every cell becomes a crisp 4 × 4 block. download({ data: await toPNG(dither, { scale: 4 }), filename: "dither.png" }); // An SVG drawn in cells, 4px each. const svg = toSVG(dither, { cell: 4 }); // A CSS rule with the dither as a covering background. const css = toCSS({ kind: "image", image: dither, selector: ".hero" }); ``` `toSVG` and `toCSS` with a tile return strings and run anywhere, including on the server. `toPNG`, `download` and the PNG form of `toCSS` need a browser, because they draw to a canvas. ## Examples [#examples] ### A fade in pure CSS [#a-fade-in-pure-css] The default kind of `toCSS` repeats one matrix tile at a coverage `level` from 0 to 1. In `mask` mode it paints the element with `ink` and masks it to the dither, so the ink can be a CSS variable that follows your theme. Four bands at falling levels make the stepped fade at the bottom of this card. The rules are built on the server; the page runs no script for them. ```tsx title="examples/export-tile.tsx" import { toCSS } from "@/lib/dither/export"; // Four bands of falling coverage make a stepped dither fade in pure CSS. // Mask mode paints with --dither-ink, so the bands follow the theme; the // rules are built on the server and the page runs no script for them. const BANDS = [0.75, 0.5, 0.25, 0.125] as const; const rules = BANDS.map((level, index) => toCSS({ cell: 4, ink: "--dither-ink", level, mode: "mask", selector: `.export-band-${index}`, }) ).join("\n"); export default function ExportTile() { return (

Changelog · v2.4

Exports got smaller

The band below is four CSS rules and no images. Each one is a 16-pixel tile at a lower coverage than the last.

); } ``` ```ts toCSS({ level: 0.25, cell: 4, ink: "--dither-ink", mode: "mask" }); ``` ```css .dither { background-color: var(--dither-ink); -webkit-mask: url("data:image/svg+xml,…") 0 0 / 16px 16px repeat; mask: url("data:image/svg+xml,…") 0 0 / 16px 16px repeat; } ``` In the default `background` mode the ink is baked into the tile and `paper` fills the gaps. An SVG inside a data URI can't read CSS variables, so pass a literal colour for `ink` in that mode. ### Indexed bitmaps [#indexed-bitmaps] If you work out the dither yourself, pass `toSVG` one palette index per cell instead of pixels. Any index without a colour is left transparent. ```ts const svg = toSVG( { width: 64, height: 32, indices }, { cell: 3, palette: ["#0a0a0b", "#3d6bff", "#dfe7ff"] } ); ``` ## API [#api] ### toPNG [#topng] ```ts toPNG( source: HTMLCanvasElement | PixelBuffer, options?: { scale?: number } ): Promise ``` Encodes a canvas or pixels as a PNG. `scale` is a whole-number upscale with no smoothing, so pass the cell size to turn one pixel per cell into a full-size image. ### toSVG [#tosvg] ```ts toSVG( source: PixelBuffer | IndexedBitmap, options?: { cell?: number; palette?: DitherColor[]; background?: boolean; } ): string ``` An SVG in cell units, scaled to `cell` pixels per cell (default 1). The most used colour becomes one background rectangle unless `background` is `false`. Rows of same-coloured cells become short relative strokes and taller blocks become rectangles, at most two ``s per colour, so a 400 × 225 ordered dither is about 150 KB before gzip. `palette` colours an `IndexedBitmap` by index. ### toCSS [#tocss] ```ts toCSS(options: CSSOptions): string ``` Returns one CSS rule. The default `tile` kind repeats one matrix tile; the `image` kind embeds a whole dither. | Prop | Type | Default | Description | | --- | --- | --- | --- | | `level` (required) | `number` | | Tile kind. Share of cells inked, 0 to 1. | | `matrix` | `DitherMatrix` | `"bayer4"` | Tile kind. The threshold pattern. | | `cell` | `number` | `3` | Tile kind. Cell size in CSS pixels. | | `ink` | `DitherColor` | `"#3b82f6"` | Tile kind. Use a literal colour in background mode; any colour in mask mode. | | `paper` | `DitherColor` | `"transparent"` | Tile kind, background mode. The colour behind the ink. | | `mode` | `"background" \| "mask"` | `"background"` | Tile kind. Bake the ink into the tile, or mask the element. | | `kind` | `"tile" \| "image"` | `"tile"` | "image" embeds a whole dither as a covering background. | | `image` (required) | `PixelBuffer` | | Image kind. The dither, one pixel per cell. | | `format` | `"png" \| "svg"` | `"png"` | Image kind. PNG needs a browser; SVG is a pure string. | | `selector` | `string` | `".dither"` | The rule's selector. | ### download [#download] ```ts download(options: { data: Blob | string; filename: string; type?: string; }): void ``` Saves a Blob or a string through the browser's download flow. Strings are saved with `type`, default `text/plain`. `svgDataUrl(svg)` is exported too: it wraps an SVG string in a CSS `url()`. ## Notes [#notes] * PNG and SVG exports match pixel for pixel at their own size. Shown smaller than one pixel per cell, an SVG drops cells in stripes, as any cell-based vector does; use PNG for thumbnails. * PNG exports are capped at 16,384 pixels on the longest side, the most every current browser can draw. A larger `scale` is reduced to fit. * The functions never change their input. # Catalog Every example component built on the dithercn engine, grouped by what it's for. > 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). Each component installs as plain source into `components/dithercn/`. Drop one into your page as it is, read its source to see how it's made, or point your agent at one and ask for a variation. For anything not here, [ask your agent](/docs#ask-your-agent-for-a-dither) or [write your own](/docs/engine/write-your-own). Every example, with its install command and docs link, is listed in [llms.txt](/llms.txt) and in the skill's [references/examples.md](/.well-known/agent-skills/dithercn/references/examples.md). ## Using an example [#using-an-example] Install any example by its registry URL. It brings the engine with it the first time: ```bash npx shadcn@latest add https://dithercn.dev/r/dither-gradient.json ``` Backgrounds fill the box you give them. Put one behind your content with `absolute inset-0`: ```tsx import { DitherGradient } from "@/components/dithercn/dither-gradient"; export function Hero() { return (

Your headline

); } ``` Backgrounds built on `DitherShader` share these props: | Prop | What it controls | Default | | --------- | --------------------------------------------------------------------------- | -------------------------------- | | `matrix` | The threshold pattern: `"bayer2"`, `"bayer4"`, `"bayer8"` or `"blue-noise"` | Varies by component | | `levels` | How many tones to snap to before dithering | Every tone the matrix can draw | | `cell` | Cell size in CSS pixels | `--dither-cell` | | `palette` | Colours from paper to ink, two or more | `--dither-paper`, `--dither-ink` | | `animate` | Whether it moves | `true` | | `speed` | How fast it moves | `1` | Charts and interface pieces take a `color` from the [named palette](/docs/theming) and a `fill` of `"gradient"`, `"dotted"`, `"hatched"` or `"solid"`. Each page lists every prop. # Dither gradient A linear, radial or conic gradient drawn as a dither, for heroes, section breaks and cards. > 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-gradient.json](https://dithercn.dev/r/dither-gradient.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/dither-gradient.json ``` ## Usage [#usage] ```tsx import { DitherGradient } from "@/components/dithercn/dither-gradient"; export function Hero() { return (

Your headline

); } ``` The gradient fills its box, so give it a size with `className`. It runs from paper at the start to ink at the end, in your theme's `--dither-paper` and `--dither-ink` colours. ## Examples [#examples] ### Glow behind a headline [#glow-behind-a-headline] A radial gradient that starts as ink and fades to paper. With the centre below the bottom edge and a small radius, only a low arc of light shows, and the headline sits on clean paper. ### Conic sweep through a palette [#conic-sweep-through-a-palette] `palette` takes any number of colours from paper to ink. A conic sweep jumps from its last colour back to its first, so this palette starts and ends on the background to hide the seam. Blue noise gives it an even, grainy texture. ### Still, inside a card [#still-inside-a-card] With `animate={false}` the gradient draws one frame and stops. `to={0.55}` keeps the densest band light enough for text to sit on, and setting `--dither-paper` to the card's own background blends the gradient into it. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `mode` | `"linear" \| "radial" \| "conic"` | `"linear"` | The gradient's shape. | | `angle` | `number` | `180 (linear), 0 (conic)` | Linear: the direction in degrees, as in CSS. Conic: where the sweep starts. | | `center` | `[x: number, y: number]` | `[0.5, 0.5]` | Radial and conic only. The centre as fractions of the box, measured from the top left. | | `radius` | `number` | `1` | Radial only. 1 reaches the farthest corner. | | `from` | `number` | `0` | Tone at the start, from 0 (paper) to 1 (ink). | | `to` | `number` | `1` | Tone at the end. | | `noise` | `number` | `0.12` | How far a slow noise drift bends the bands. 0 gives straight, even bands. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--dither-ink"]` | Colours from paper to ink: palette names such as "green", CSS variable names or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `levels` | `number` | `All the matrix can draw` | Tones the gradient snaps to before dithering. Lower values give visible bands. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the noise drifts. | | `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 gradient fills this box. | ## Notes [#notes] * The gradient 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 smooth CSS gradient in the same colours. * One gradient costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Dither glow A soft dithered spotlight from an edge, a corner or any point, for heroes, pricing cards and quiet section backgrounds. > 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-glow.json](https://dithercn.dev/r/dither-glow.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/dither-glow.json ``` ## Usage [#usage] ```tsx import { DitherGlow } from "@/components/dithercn/dither-glow"; export function Hero() { return (

Your headline

); } ``` The glow fills its box and shines from `position`. It uses `bayer8`, whose 64 tones keep the falloff smooth. Its centre is brightest and it falls off softly, with a slow breath and a little noise at the edge so it never looks like a flat circle. ## Examples [#examples] ### Rising behind a headline [#rising-behind-a-headline] With `position="bottom"` the light sits just below the edge, so only its top shows. The headline sits near the top, on clean paper. ### Corner of a card [#corner-of-a-card] A corner glow is the quietest way to lift one card out of a row. Setting `--dither-paper` to the card's background blends the glow into it, and `bayer8` gives a finer grain at small sizes. ### Any point, any palette [#any-point-any-palette] `position` also takes `[x, y]` as fractions of the box. With a longer palette the glow steps through each colour from its edge to its core. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `position` | `"top" \| "bottom" \| "left" \| "right" \| "center" \| "top-left" \| "top-right" \| "bottom-left" \| "bottom-right" \| [x: number, y: number]` | `"top"` | Where the light comes from. Edges sit just outside the box; a point is measured from the top left. | | `radius` | `number` | `0.7` | How far the light reaches. 1 reaches the farthest corner. | | `intensity` | `number` | `0.75` | Brightness at the centre, from 0 (none) to 1 (full ink). | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--dither-ink"]` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer8"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it 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 background fills 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 shows a CSS approximation in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Mesh gradient Three to five colour blobs that drift and melt into each other, drawn as a dither, for heroes and sign-up screens. > 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/mesh-gradient.json](https://dithercn.dev/r/mesh-gradient.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/mesh-gradient.json ``` ## Usage [#usage] ```tsx import { MeshGradient } from "@/components/dithercn/mesh-gradient"; export function Hero() { return (

Your headline

); } ``` Each blob drifts on its own slow orbit. Where two meet, their colours blend through the palette before the dither, so the seams read as soft gradients instead of edges. The default palette uses tints of your theme's `--dither-ink` with a touch of violet. ## Examples [#examples] ### Five blobs, warm palette [#five-blobs-warm-palette] `palette` starts with paper and then takes one colour per blob. Pass `blobs + 1` colours to give each blob its own; the order sets which colours sit next to each other where blobs meet. Blue noise, the default, keeps the grain even across so many tones. ### Behind a form [#behind-a-form] Three large blobs, bigger cells and a slower `speed` make a calm backdrop. The form sits on a translucent panel so the colour shows around it without hurting the text. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `blobs` | `3 \| 4 \| 5` | `4` | How many colour blobs drift across the box. | | `size` | `number` | `0.6` | Size of each blob as a share of the box height. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, then four tints of --dither-ink` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"blue-noise"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it 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 background fills 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 shows a CSS approximation in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Aurora Curtains of light that fold, sway and shimmer, drawn as a dither, with knobs for colour, rays, tilt and drift. > 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/aurora.json](https://dithercn.dev/r/aurora.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/aurora.json ``` ## Usage [#usage] ```tsx import { Aurora } from "@/components/dithercn/aurora"; export function Hero() { return (

Your headline

); } ``` Each curtain has a bright lower edge that folds slowly, with rays that fade as they rise. Further curtains hang higher and dimmer. `height` sets where the lowest edge hangs, so you can keep the area under it clear for text. The props group into three jobs: * Shape: `curtains`, `height`, `spread`, `sway`, `turbulence`, `rays`, `length`. * Light and colour: `color` for the edges, `tipColor` for the tops of the rays, `background`, `glow`, `intensity`, `shimmer`. Pass `palette` to set every tone yourself. * Placement and motion: `tilt`, `scale`, `drift`, `direction`, `mask`, plus the engine's `speed`, `cell` and `matrix`. ## Examples [#examples] ### Above a headline [#above-a-headline] Raising `height` lifts the curtains off the lower third, where the headline sits on clean paper. A `tipColor` of `"blue"` shifts the tops of the rays to a second, cooler hue, and `mask="edges"` fades the sides. ### Northern lights [#northern-lights] Your own colours turn it into the real thing: green edges, violet tips and a night-sky `background`. Four curtains, longer rays, a slight `tilt` and a little `drift` give it depth. ### Hanging over a call to action [#hanging-over-a-call-to-action] `direction="down"` flips the curtains so the rays fall from the top and the button sits on clear paper below. Few broad `rays` and 4px cells give it a coarser, poster-like grain. ### Still, in a banner [#still-in-a-banner] With `animate={false}` it draws one frame. `stillTime` picks which moment, so you can choose a shape you like. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `color` | `DitherColor` | `"--dither-ink"` | The bright lower edge of each curtain. | | `tipColor` | `DitherColor` | `color` | The faint tops of the rays. Set a second colour for a two-tone sky. | | `background` | `DitherColor` | `"--dither-paper"` | The sky behind. | | `curtains` | `number` | `3` | How many curtains, from 1 to 6. Each one hangs higher and dimmer. | | `height` | `number` | `0.42` | Where the lowest bright edge hangs, from 0 (bottom) to 1 (top). | | `spread` | `number` | `0.11` | Vertical gap between curtains, from 0 to 0.3. | | `sway` | `number` | `1` | How far the edges fold. 0 is straight, 3 is deep folds. | | `turbulence` | `number` | `0.6` | Fine ripples along each edge, from 0 to 2. | | `rays` | `number` | `8` | How many rays fit across. 2 gives broad bands, 20 fine needles. | | `length` | `number` | `0.45` | How far the rays reach up, from 0 to 1. | | `glow` | `number` | `0.9` | Brightness of the line along each edge, from 0 to 2. | | `intensity` | `number` | `0.95` | Brightness of the curtains, from 0 to 1. | | `shimmer` | `number` | `0.2` | A slow brightness ripple, from 0 to 1. | | `scale` | `number` | `1` | Horizontal zoom. Higher values fit more folds across. | | `tilt` | `number` | `0` | Tilt of the curtains in degrees, from -45 to 45. | | `drift` | `number` | `0` | Sideways travel, from -2 (left) to 2 (right). | | `direction` | `"up" \| "down"` | `"up"` | "down" flips it so the rays hang from a bright top edge. | | `mask` | `"none" \| "top" \| "bottom" \| "edges"` | `"none"` | Fades it to paper where your text sits. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Built from the colour props` | Takes over the colours: paper first, brightest last. Palette names, CSS variable names or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer8"` | The threshold pattern. See Dither engine. | | `levels` | `number` | `All the matrix can draw` | Tones the field snaps to before dithering. Low values give hard steps. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it moves. | | `animate` | `boolean` | `true` | false stops the motion: a still frame if it starts off, the current frame if it was moving. | | `stillTime` | `number` | `0` | The moment drawn when motion is off or reduced, in seconds. | | `className` | `string` | `u2013` | Size and position. The background fills 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 shows a CSS approximation in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. More `curtains` cost a little more per pixel. See [Performance](/docs/engine/performance). # Noise field A slow, folding noise texture drawn as a dither, in four styles, with a mask that clears space for the text on top. > 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/noise-field.json](https://dithercn.dev/r/noise-field.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/noise-field.json ``` ## Usage [#usage] ```tsx import { NoiseField } from "@/components/dithercn/noise-field"; export function Section() { return (

Your headline

); } ``` The field is fractal noise folded back on itself, so it flows like smoke rather than scrolling. `variant` changes what the noise draws: * `smoke`, the default: soft folds that dither into smooth gradients. * `ridges`: sharp creases, like veins or lightning. * `contours`: topographic lines, spaced by `bands`. * `marble`: warped stripes, also set by `bands`. `warp`, `detail` and `scale` shape the noise itself. `contrast`, `coverage` and `intensity` decide how much of it turns to ink. `angle` and `flow` set where it drifts, and `mask` with `maskSize` clears room for your text. ## Examples [#examples] ### Cleared under a headline [#cleared-under-a-headline] `mask="center"` fades the field to paper in the middle of the box. Use `"top"` or `"bottom"` when the text sits at one end, and `maskSize` to clear more or less. ### Fine grain in a dashboard card [#fine-grain-in-a-dashboard-card] A high `scale`, a low `intensity` and 2px cells turn it into a texture you notice only when it moves. ### Topographic lines [#topographic-lines] `variant="contours"` draws the noise as height lines, a good fit for maps and region stats. A bottom `mask` keeps the numbers on clean paper, and a low `flow` lets the terrain shift slowly. ### Marble with a highlight [#marble-with-a-highlight] `variant="marble"` with more `warp` bends the stripes into veins. `highlight` adds a third colour for the brightest peaks, here the foreground, so the stripes catch the light. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `color` | `DitherColor` | `"--dither-ink"` | The ink of the texture. | | `highlight` | `DitherColor` | `u2013` | A third colour for the brightest peaks. Leave it out for two tones. | | `background` | `DitherColor` | `"--dither-paper"` | The paper behind. | | `variant` | `"smoke" \| "ridges" \| "contours" \| "marble"` | `"smoke"` | The shape of the texture: soft folds, sharp creases, topographic lines or warped stripes. | | `scale` | `number` | `1.3` | Size of the features. Higher values give smaller, busier noise. | | `warp` | `number` | `1.7` | How far the noise folds back on itself, from 0 (plain noise) to 4. | | `detail` | `number` | `4` | Octaves of noise, from 1 (smooth blobs) to 6 (fine grain on top). | | `contrast` | `number` | `1.2` | 1 is soft. 2 and up give hard-edged islands. | | `coverage` | `number` | `0` | How much of the box is inked, from -0.5 (sparse) to 0.5 (dense). | | `intensity` | `number` | `0.9` | Brightest tone the field reaches, from 0 to 1. | | `bands` | `number` | `8` | Lines for "contours" and stripes for "marble", from 2 to 24. | | `angle` | `number` | `90` | Direction it drifts in, in degrees. 0 is right, 90 is up. | | `flow` | `number` | `1` | How fast it drifts along angle, from 0 to 4. | | `invert` | `boolean` | `false` | Swaps paper and ink. | | `mask` | `"none" \| "center" \| "top" \| "bottom" \| "edges"` | `"none"` | Fades the field out where your text sits. "edges" keeps only the middle. | | `maskSize` | `number` | `0.5` | How much of the box the mask clears, from 0 to 1. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Built from the colour props` | Takes over the colours: paper first, brightest last. Palette names, CSS variable names or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer8"` | The threshold pattern. See Dither engine. | | `levels` | `number` | `All the matrix can draw` | Tones the field snaps to before dithering. Low values give hard steps. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it moves. | | `animate` | `boolean` | `true` | false stops the motion: a still frame if it starts off, the current frame if it was moving. | | `stillTime` | `number` | `0` | The moment drawn when motion is off or reduced, in seconds. | | `className` | `string` | `u2013` | Size and position. The background fills 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 shows a CSS approximation in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. Each step of `detail` adds a layer of noise, so lower it on large, busy pages. See [Performance](/docs/engine/performance). # Waves Layered swells under a glowing horizon, or thin lines like a sound wave, drawn as a dither. > 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/waves.json](https://dithercn.dev/r/waves.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/waves.json ``` ## Usage [#usage] ```tsx import { Waves } from "@/components/dithercn/waves"; export function Footer() { return (
); } ``` Rows of swells roll towards you below the horizon. Distant rows are small and bright, near ones large and dark, and every crest has a one-cell highlight. ## Examples [#examples] ### Dusk [#dusk] A lower `horizon`, eight `layers` and a warm palette give a sea at sunset. A smaller `amplitude` calms the water. ### Sound wave [#sound-wave] `variant="lines"` draws thin lines that swell in the middle and taper at both ends. It suits audio players, voice input and anything that listens. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `variant` | `"ocean" \| "lines"` | `"ocean"` | Swells under a horizon, or thin lines like a sound wave. | | `horizon` | `number` | `0.62` | Ocean only. Height of the horizon, from 0 (bottom) to 1 (top). | | `layers` | `number` | `6` | How many waves, from 2 to 8. | | `amplitude` | `number` | `1` | Height of the waves. 1 is calm, 2 is rough. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, then tints of --dither-ink` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it 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 background fills 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 shows a CSS approximation in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Plasma Classic plasma: overlapping sine waves that flow into each other, dithered in two colours or a whole palette. > 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/plasma.json](https://dithercn.dev/r/plasma.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/plasma.json ``` ## Usage [#usage] ```tsx import { Plasma } from "@/components/dithercn/plasma"; export function Hero() { return (

Your headline

); } ``` Plasma fills its box, so give it a size with `className`. Four sine waves, one of them circling the centre, add up to a pattern that never repeats. A slow noise warp bends it so it doesn't look mechanical. By default it runs from `--dither-paper` through `--chart-5` to `--dither-ink`. ## Examples [#examples] ### Through a palette [#through-a-palette] Pass any number of colours from paper to ink. More `bands` cycle through the palette more often, and blue noise swaps the Bayer grid for an even grain. ### Behind a headline [#behind-a-headline] `intensity` caps the brightest tone, so the crests never reach full ink and the text stays readable. A smaller `scale` and more `warp` give fewer, softer shapes. ### Coarse and still [#coarse-and-still] Large cells, the 2×2 matrix and only four `levels` give hard contour bands like an old demo-scene effect. `animate={false}` holds one frame. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `scale` | `number` | `1` | Size of the pattern. Higher values pack more waves in. | | `warp` | `number` | `0.3` | How far slow noise bends the waves, from 0 (pure sines) to 1. | | `bands` | `number` | `1.5` | How many times the colours cycle across one wave. | | `intensity` | `number` | `1` | The brightest tone, from 0 to 1. Lower it to put text on top. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--chart-5", "--dither-ink"]` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `levels` | `number` | `All the matrix can draw` | Tones the plasma snaps to before dithering. Lower values give hard contour bands. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the waves move. | | `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 plasma fills this box. | ## Notes [#notes] * The plasma 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 soft CSS blobs in the same colours. * One draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Beams God rays: shafts of light from a point or an edge that sweep slowly and fade with distance. > 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/beams.json](https://dithercn.dev/r/beams.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/beams.json ``` ## Usage [#usage] ```tsx import { Beams } from "@/components/dithercn/beams"; export function Hero() { return (

Your headline

); } ``` Light comes from `origin` and spreads in a cone around `angle`. The rays are noise around the origin, so they widen with distance like real shafts of light, and dust drifting through them breaks them up along their length. The whole cone sways back and forth by `sweep` degrees over about a minute. ## Examples [#examples] ### From the top edge [#from-the-top-edge] An origin far above the box gives nearly parallel rays, like light through a high window. Brightness fades from where the light enters the box, not from the far origin. ### Spotlight over a headline [#spotlight-over-a-headline] A narrow cone from just above the centre, through a four-colour palette, with a short `reach` so the text at the bottom sits in the dark. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `origin` | `[x: number, y: number]` | `[0.18, -0.12]` | Where the light comes from, as fractions of the box from the top left. Values outside 0 to 1 put it out of frame. | | `angle` | `number` | `160` | Direction the light points, in degrees as in CSS: 180 is straight down. | | `spread` | `number` | `80` | Width of the cone in degrees. | | `rays` | `number` | `9` | Roughly how many separate rays. | | `sweep` | `number` | `6` | How far the cone sways each way, in degrees. 0 holds it still. | | `reach` | `number` | `1` | How far the light reaches from where it enters the box, as a share of the box diagonal. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--dither-ink"]` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `levels` | `number` | `All the matrix can draw` | Tones before dithering. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the rays drift and sway. | | `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 light fills this box. | ## Notes [#notes] * The light 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 soft CSS cone. # Silk Folds of silk catching the light: smooth sheets that ripple and shift, with a bright highlight along each crest. > 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/silk.json](https://dithercn.dev/r/silk.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/silk.json ``` ## Usage [#usage] ```tsx import { Silk } from "@/components/dithercn/silk"; export function Hero() { return (

Your headline

); } ``` The cloth is a height field of folds bent by slow waves. It is lit from the top left: faces turned to the light are bright, the far sides of each fold fall smoothly into shadow, and a narrow highlight runs along every crest. The folds slide and change width over time. It defaults to the 8×8 matrix, which suits its long soft gradients. ## Examples [#examples] ### Behind a headline [#behind-a-headline] Fewer `folds`, a sharper `sheen` and an `intensity` of 0.45 keep the brightest crest below full ink. ### Champagne [#champagne] Eight folds on a diagonal through four warm golds, from near-black to a pale cream highlight. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `folds` | `number` | `6` | How many folds cross the box. | | `angle` | `number` | `110` | Direction the folds run, in degrees as in CSS: 90 runs them left to right. | | `sheen` | `number` | `0.6` | How narrow and bright the highlight on each fold is, from 0 to 1. | | `intensity` | `number` | `1` | The brightest tone, from 0 to 1. Lower it to put text on top. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, two tints of --dither-ink, then --dither-ink` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer8"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the folds move. | | `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 cloth fills 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 shows soft diagonal stripes in CSS. # Smoke Soft smoke that billows upward and thins out, lit from above, drawn as a dither. > 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/smoke.json](https://dithercn.dev/r/smoke.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/smoke.json ``` ## Usage [#usage] ```tsx import { Smoke } from "@/components/dithercn/smoke"; export function Footer() { return (
); } ``` The smoke is thickest at the bottom and thins out as it climbs. Light from the upper left catches one side of each plume and leaves the other in shadow. The thickest parts stop short of solid ink, so the dither always shows. ## Examples [#examples] ### Behind a headline [#behind-a-headline] A lower `height` keeps the smoke in the bottom half, and a lower `density` makes it lighter. Negative `wind` blows it to the left. ### Embers [#embers] Four warm colours turn the smoke into the glow above a fire. Blue noise gives an even grain instead of the Bayer grid. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `scale` | `number` | `1` | Size of the plumes. Higher values make more, smaller ones. | | `height` | `number` | `0.8` | How high the smoke climbs before it thins out, from 0 to 1. | | `wind` | `number` | `0.2` | Sideways drift. Negative blows left, positive right. | | `density` | `number` | `1` | How thick the smoke is, from 0 to 1.5. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--dither-ink"]` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it 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 background fills 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 shows a soft CSS haze in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Contours A topographic map drawn as a dither: contour lines over slowly drifting hills, with heavier index lines and a light hillshade. > 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/contours.json](https://dithercn.dev/r/contours.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/contours.json ``` ## Usage [#usage] ```tsx import { Contours } from "@/components/dithercn/contours"; export function Section() { return (

Your heading

); } ``` Contour lines trace even steps of height across a field of low hills. Every fifth line is drawn heavier, as on a survey map, and slopes facing away from a light in the top left pick up a light stipple. The hills drift and slowly change shape. They keep their size in pixels, so a narrow box shows a smaller piece of the same map rather than a squashed one. ## Examples [#examples] ### Survey map [#survey-map] More `lines`, a smaller `scale` and a little more `relief` in green and brown give an old ordnance survey sheet. `cell={2}` keeps the lines fine. ### Behind a headline [#behind-a-headline] Set `index` and `relief` to 0 for plain lines, and pass two soft tints of the ink so the lines never reach full contrast. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `lines` | `number` | `14` | How many contour lines from the lowest ground to the highest, from 4 to 32. | | `scale` | `number` | `1` | Size of the hills. Higher values pack more of them in. | | `index` | `number` | `5` | Every how many lines one is drawn heavier. 0 draws them all the same. | | `relief` | `number` | `0.5` | How strongly slopes facing away from the light are shaded, from 0 to 1. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, a tint of --dither-ink, then --dither-ink` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the hills drift. | | `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 map fills 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 shows faint CSS rings. # Ripples Concentric rings spreading from one to four points and crossing like drops on still water, drawn as a dither. > 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/ripples.json](https://dithercn.dev/r/ripples.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/ripples.json ``` ## Usage [#usage] ```tsx import { Ripples } from "@/components/dithercn/ripples"; export function Hero() { return (

Your headline

); } ``` Each point in `sources` sends out rings that fade with distance. A lone ring stops at the middle colour; where rings from two points cross, they add up to full ink, so the interference pattern shows up as bright knots. A slow swell bends the rings slightly so they don't look drawn with a compass. ## Examples [#examples] ### One drop [#one-drop] A single source just below the frame sends rings up the box. They fade out before they reach the text at the top, and a little less `intensity` keeps the nearest ones soft. ### Rain [#rain] Four sources, tighter rings and a faster `decay` through the chart colours. The 8×8 matrix gives the fading rings a finer grain. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `sources` | `[x: number, y: number][]` | `[[0.28, 0.38], [0.74, 0.66]]` | Where the rings start, as fractions of the box from the top left. One to four points; values outside 0 to 1 put a point out of frame. | | `rings` | `number` | `12` | How many rings fit in the height of the box. | | `decay` | `number` | `1` | How quickly the rings fade away from their source. 0 keeps them strong across the box. | | `intensity` | `number` | `1` | The brightest tone, from 0 to 1. Lower it to put text on top. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, a tint of --dither-ink, then --dither-ink` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the rings travel outwards. | | `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 rings 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 shows rings drawn with CSS gradients. # Ridgelines Stacked ridge lines like a pulsar plot, each hiding the ones behind it, drawn as a dither. > 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/ridgelines.json](https://dithercn.dev/r/ridgelines.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/ridgelines.json ``` ## Usage [#usage] ```tsx import { Ridgelines } from "@/components/dithercn/ridgelines"; export function Section() { return (
); } ``` Each line is flat at the ends and peaked in the middle, and each one hides the lines behind it. The peaks shift slowly. A faint shade under the tallest peaks makes them read as solid ridges. Lines stay one cell wide and at least four cells apart, so a short box draws fewer of them. ## Examples [#examples] ### In a card [#in-a-card] Fewer `lines`, a narrower `spread` and small cells fit a pulsar plot in a card. ### Mountain range [#mountain-range] `spread={1}` raises peaks across the whole width. A lower `amplitude` keeps them low, and a three-colour palette shades the slopes. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `lines` | `number` | `32` | How many lines, from 6 to 64. A short box draws fewer. | | `amplitude` | `number` | `1` | Height of the peaks. 2 is twice as tall. | | `spread` | `number` | `0.5` | Width of the peaked band, as a share of the box, from 0.1 to 1. | | `shade` | `number` | `0.25` | Faint tone under the raised peaks, from 0 to 0.5. 0 keeps only the lines. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--dither-ink"]` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it 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 background fills 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 shows thin CSS lines in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Metaballs Soft blobs that drift, merge and split like a lava lamp, drawn as a dither. > 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/metaballs.json](https://dithercn.dev/r/metaballs.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/metaballs.json ``` ## Usage [#usage] ```tsx import { Metaballs } from "@/components/dithercn/metaballs"; export function Hero() { return (

Your headline

); } ``` Each blob rises and sinks on its own slow loop. Where two come close, their edges reach for each other and join, then pull apart again. Every blob is densest at its core and has a soft halo around its edge. ## Examples [#examples] ### Contour rings [#contour-rings] `variant="rings"` draws contour lines around the blobs instead of filling them. The rings merge and split with the blobs. ### Lava lamp [#lava-lamp] Five blobs in warm colours, sped up a little, in a tall rounded box. In a narrow box the blobs are sized by its width. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `variant` | `"solid" \| "rings"` | `"solid"` | Shaded blobs with a soft halo, or contour lines around them. | | `count` | `number` | `5` | How many blobs, from 2 to 8. | | `size` | `number` | `1` | Size of the blobs. In a tall box they are sized by the width. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--dither-ink"]` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it 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 background fills 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 shows soft CSS blobs in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Tunnel A slow flight down a tunnel of round or square rings, drawn as a dither. > 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/tunnel.json](https://dithercn.dev/r/tunnel.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/tunnel.json ``` ## Usage [#usage] ```tsx import { Tunnel } from "@/components/dithercn/tunnel"; export function Hero() { return (

Your headline

); } ``` Rings come out of the fog at the vanishing point and grow as they pass you. Spokes run along the walls, and every other panel between them is lit, like tiles. The middle stays empty, so a headline fits there. ## Examples [#examples] ### Behind a headline [#behind-a-headline] `spokes={0}` leaves only the rings, and fewer `rings` spread them out. A larger `fog` widens the empty middle so the text sits clear of the rings. ### Square corridor [#square-corridor] `shape="square"` turns the rings into frames. Moving the `center` up a little and lowering the `sway` gives a steadier corridor, and a three-colour palette lights the near walls. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `"circle" \| "square"` | `"circle"` | The cross-section of the tunnel. | | `rings` | `number` | `1` | How close together the rings are. 2 packs twice as many in. | | `spokes` | `number` | `12` | Lines along the walls, from 0 to 32. 0 leaves only the rings and drops the tiles. | | `center` | `[number, number]` | `[0.5, 0.5]` | The vanishing point, as fractions of the box from the top left. | | `sway` | `number` | `1` | How far the vanishing point wanders. 0 holds it still. | | `fog` | `number` | `1` | Size of the empty fog in the middle. 2 doubles it to clear room for text. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--dither-ink"]` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast it 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 background fills 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 shows concentric CSS rings in the same colours. * It costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance). # Retro grid A perspective grid floor scrolling towards a horizon under a striped, glowing sun, drawn as a dither. > 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/retro-grid.json](https://dithercn.dev/r/retro-grid.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/retro-grid.json ``` ## Usage [#usage] ```tsx import { RetroGrid } from "@/components/dithercn/retro-grid"; export function Hero() { return (

Your headline

); } ``` The floor is a grid seen from a low camera, scrolling slowly towards you. Lines stay about one cell wide at every depth and fade out where they would crowd closer than a few cells, so the horizon doesn't break into moiré. The sun sits half sunk behind the horizon, with slits through its lower half that thicken towards the ground and drift down. Grid squares keep their size against the box height, so a phone shows fewer columns, not thinner ones. ## Examples [#examples] ### Sunset [#sunset] Four warm colours, from a deep violet paper through magenta to a pale gold sun. A bigger `sun` and `cell={4}` give it the chunky look of an old game. ### Without the sun [#without-the-sun] `sun={0}` leaves only the glow along the horizon, which clears the sky for a headline. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `horizon` | `number` | `0.42` | Height of the horizon, from 0 (bottom) to 1 (top). | | `sun` | `number` | `0.4` | Size of the sun as a share of the box height. 0 hides it. | | `columns` | `number` | `12` | How many squares across the front of the floor in a 16:10 box. Narrower boxes show fewer. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, two tints of --dither-ink, then --dither-ink` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the floor scrolls and the sun's slits drift. | | `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 scene fills 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 shows a horizon line, a soft sun and a fade in CSS. # Eclipse A dark disc with a glowing rim and a soft corona that breathes slowly, for launch pages and event heroes. > 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/eclipse.json](https://dithercn.dev/r/eclipse.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/eclipse.json ``` ## Usage [#usage] ```tsx import { Eclipse } from "@/components/dithercn/eclipse"; export function Hero() { return (

Your headline

); } ``` A disc in `skyColor` sits in front of a light in `rimColor`. A thin rim hugs the edge and is brightest on the `light` side, like the crescent of a real eclipse. Around it, a halo fades outwards with slow variations in strength, and the whole glow breathes on a slow ten-second cycle. ## Examples [#examples] ### Beside a headline [#beside-a-headline] The disc sits in the top right with a short corona, lit from the lower left, and the text sits in the clear space below it. This layout holds from phone to desktop widths. ### Amber on warm black [#amber-on-warm-black] Explicit colours instead of theme tokens, a smaller disc, a wider corona and the full `pulse`. The 8×8 matrix gives a finer texture. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `center` | `[x: number, y: number]` | `[0.5, 0.5]` | Centre of the disc, as fractions of the box from the top left. | | `size` | `number` | `0.24` | Radius of the disc as a share of the box height. | | `corona` | `number` | `0.5` | How far the halo reaches past the rim, from 0 to 1. | | `light` | `number` | `315` | Where the rim is brightest, in degrees as in CSS: 0 is the top. | | `pulse` | `number` | `0.5` | How much the glow breathes, from 0 (steady) to 1. | | `rimColor` | `DitherColor` | `"--dither-ink"` | Colour of the rim and halo: a palette name such as "orange", a CSS variable name, or any CSS colour. | | `skyColor` | `DitherColor` | `"--dither-paper"` | Colour of the sky and the disc. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the halo shifts and breathes. | | `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 eclipse fills this box. | ## Notes [#notes] * The eclipse 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 disc and ring. * The disc is the sky colour, so on a light theme it reads as a pale disc with a coloured ring. Pass a dark `skyColor` for a dark disc on any theme. # Horizon A planet's horizon seen from space, with a lit rim, a drifting aurora glow and fixed stars. The landing page hero, with props. > 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/horizon.json](https://dithercn.dev/r/horizon.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/horizon.json ``` ## Usage [#usage] ```tsx import { Horizon } from "@/components/dithercn/horizon"; export function Hero() { return (

Your headline

); } ``` This is the scene behind the dithercn landing page. The planet is a circle whose top touches `horizon`; `curve` sets how small and round it looks. Above the rim, domain-warped noise forms aurora curtains that slide slowly along the horizon. The sky and planet use `skyColor`, and the glow, rim and stars use `glowColor`. ## Examples [#examples] ### Sunrise [#sunrise] A flatter planet, higher in the frame, with a smooth band instead of curtains and an explicit amber glow. ### Small planet under a headline [#small-planet-under-a-headline] A low, strongly curved horizon leaves most of the frame as sky, with more stars. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `horizon` | `number` | `0.28` | Height of the horizon's highest point from the bottom, from 0 to 1. | | `curve` | `number` | `0.6` | How round the planet is, from 0 (nearly flat) to 1 (a small globe). | | `glow` | `number` | `1` | Strength of the glow above the horizon, from 0 to 1. | | `aurora` | `number` | `1` | How much the glow breaks into curtains, from 0 (a smooth band) to 1. | | `stars` | `number` | `1` | How many stars, from 0 to 3. They never move. | | `glowColor` | `DitherColor` | `"--dither-ink"` | Colour of the glow, rim and stars: a palette name such as "orange", a CSS variable name, or any CSS colour. | | `skyColor` | `DitherColor` | `"--dither-paper"` | Colour of the sky and the planet. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the aurora drifts. | | `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 scene fills this box. | ## Notes [#notes] * The scene 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 soft CSS glow over a dark arc. * Stars thin out near the horizon, so the glow always has a clean edge. # Starfield Dithered stars in three parallax layers that twinkle and drift slowly, for space themes, sign-in pages and launch pages. > 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/starfield.json](https://dithercn.dev/r/starfield.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/starfield.json ``` ## Usage [#usage] ```tsx import { Starfield } from "@/components/dithercn/starfield"; export function Hero() { return (

Your headline

); } ``` Stars sit on three grids of different sizes. Far stars are small, dim and slow; near stars are brighter, move faster and the brightest get a small cross. A faint haze of dithered dust drifts behind them. Each star snaps to one palette colour, so it never flickers between dither thresholds as it moves. The last palette colour is the brightest star, the middle ones are dimmer stars and the haze. ## Examples [#examples] ### Behind a sign-in form [#behind-a-sign-in-form] A slower drift to the lower left and a stronger haze. The form has a translucent background so stars show through its edges. ### A quiet sky [#a-quiet-sky] `drift={0}` holds the stars in place, and `twinkle={1}` lets them pulse fully. No haze, fewer stars, in neutral foreground colours. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `density` | `number` | `1` | How many stars, from 0 to 2. | | `twinkle` | `number` | `0.6` | How much each star pulses, from 0 (steady) to 1. | | `angle` | `number` | `270` | Direction the stars drift, in degrees as in CSS: 0 is up, 90 right. | | `drift` | `number` | `1.6` | Drift speed of the nearest layer, in cells per second. The others move at 0.6 and 0.3 of it. 0 holds them still. | | `nebula` | `number` | `0.5` | Strength of the dust haze behind the stars, from 0 to 1. | | `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--chart-2", "--dither-ink"]` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"blue-noise"` | The threshold pattern. See Dither engine. | | `levels` | `number` | `16 per palette step` | Tones before dithering. The default keeps every palette colour exact so stars stay solid. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast time runs, for both drift and twinkle. | | `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 starfield fills this box. | ## Notes [#notes] * The starfield 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 tiled CSS dots. * Twinkle rates are random per star and slow, between about 3 and 12 seconds a pulse, so nothing flashes. # Halftone A print halftone: round dots on a rotated screen that swell and shrink with a source, in one ink or up to three overlapping like CMYK. > 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/halftone.json](https://dithercn.dev/r/halftone.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/halftone.json ``` ## Usage [#usage] ```tsx import { Halftone } from "@/components/dithercn/halftone"; export function Hero() { return (

Your headline

); } ``` 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 [#examples] ### Spotlight behind a headline [#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 [#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 [#three-ink-print] A coarse 16px screen in three inks behind an editorial block. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `color` | `DitherColor` | `"ink"` | Colour of the dots: a palette name such as "blue", a CSS variable name, or any CSS colour. | | `channels` | `[DitherColor, DitherColor, DitherColor?]` | `–` | Two or three inks on separate screens, 30° apart. Overlaps mix. Use instead of color. | | `background` | `DitherColor` | `"--dither-paper"` | Colour behind the dots. "transparent" lays the screen over the content underneath. | | `source` | `"noise" \| "radial" \| "linear"` | `"noise"` | What sizes the dots: soft drifting clouds, distance from a point, or a ramp. | | `center` | `[x: number, y: number]` | `[0.5, 0.5]` | Radial only. Where the dots are largest, as fractions of the box from the top left. | | `radius` | `number` | `1` | Radial only. Where the dots run out. 1 reaches the farthest corner. | | `direction` | `number` | `0` | Linear only. Direction the dots grow towards, in degrees as in CSS. | | `size` | `number` | `10` | Distance between dot centres in CSS pixels. | | `angle` | `number` | `45` | Screen angle in degrees. Each extra channel adds 30°. | | `intensity` | `number` | `0.9` | Size of the largest dot, 0 to 1. At 1 the biggest dots just touch. | | `scale` | `number` | `1` | Noise only. Size of the clouds. | | `drift` | `number` | `1` | How fast the screen slides along its angle. 0 holds the screen still. | | `speed` | `number` | `1` | How fast the source moves. | | `animate` | `boolean` | `true` | false stops the motion: a still frame if it starts off, the current frame if it was moving. | | `cell` | `number` | `one device pixel` | Size of one rendered pixel in CSS pixels. Larger cells give blockier dots. | | `className` | `string` | `–` | Size and position. The halftone fills this box. | ## Notes [#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-mix` in oklch, so they follow the theme along with the inks. # Dither globe A rotating dithered globe with a lit terminator, pulsing markers and arcs, for global sections of a landing page. > 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-globe.json](https://dithercn.dev/r/dither-globe.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/dither-globe.json ``` ## Usage [#usage] ```tsx import { DitherGlobe } from "@/components/dithercn/dither-globe"; const LONDON = [51.51, -0.13] as const; const TOKYO = [35.68, 139.69] as const; export function Global() { return ( ); } ``` The globe is drawn on a 2D canvas at dither-cell resolution: every cell on the sphere gets a tone from the land mask and the light, then an ordered threshold decides whether it's inked. Land is bright on the day side and fades to a sparse grid past the terminator; the sea is a faint dither that thickens towards the rim. It uses no WebGL context, so it doesn't count against the page's shader budget. The globe stays round and centred in its box and takes 84% of the shorter side. Make the box larger than its section and offset it to show only the top of the globe, as in the first example. Visitors can turn the globe. Dragging sideways spins it and dragging up or down tilts it, with the land staying under the pointer. Let go mid-drag and it coasts to a stop, then picks its idle spin back up. On phones, a horizontal swipe turns it and a vertical swipe still scrolls the page. When it has keyboard focus, the arrow keys turn it. Set `draggable={false}` for a globe that should only spin on its own. Land comes from the shared `world-land` mask (Natural Earth, public domain), installed with the component. Nothing is fetched at runtime. ## Examples [#examples] ### Horizon behind a headline [#horizon-behind-a-headline] A globe 150% of the section's width, pushed below the bottom edge so only its lit top shows. ### Beside copy [#beside-copy] A square globe in a two-column section. Drag it to spin. ### Turned to one place, in a card [#turned-to-one-place-in-a-card] `speed={0}` and `start` at the marker's longitude hold one region in view while the marker keeps pulsing. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `color` | `DitherColor` | `"ink"` | Colour of the land, sea and atmosphere. | | `accent` | `DitherColor` | `"orange"` | Colour of markers and arcs that don't set their own. | | `markers` | `{ at: [lat, lon]; color?: DitherColor }[]` | `[]` | Pulsing locations. Markers on the far side are hidden. | | `arcs` | `{ from: [lat, lon]; to: [lat, lon]; color?: DitherColor }[]` | `[]` | Great-circle arcs lifted off the surface, with a stretch of light running along them. | | `speed` | `number` | `6` | Degrees of rotation per second. Negative spins the other way. | | `start` | `number` | `10` | Longitude facing the viewer at the start, in degrees. | | `tilt` | `number` | `18` | How far the north pole leans towards the viewer, in degrees. | | `light` | `number` | `300` | Where the light comes from, in degrees clockwise from the top. | | `ocean` | `number` | `0.18` | Brightness of the sea on the lit side, 0 to 1. | | `atmosphere` | `boolean` | `true` | A thin dithered glow around the lit rim. | | `draggable` | `boolean` | `true` | Drag or use the arrow keys to turn and tilt the globe. It coasts after a drag, then eases back into its idle spin. | | `cell` | `number` | `2` | Size of one dither cell in CSS pixels. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Threshold matrix. | | `animate` | `boolean` | `true` | Spin and pulse. false draws one still frame. | ## Notes [#notes] * A draggable globe is focusable and announced as an image labelled "Globe. Drag it or use the arrow keys to turn it." Pass your own `aria-label` to say what it shows. With `draggable={false}` it's decorative and hidden from assistive technology, so say what it shows in the copy next to it. * With reduced motion turned on, it draws one still frame. Dragging and the arrow keys still turn it, without the coasting. * It pauses when scrolled out of view or when the tab is hidden. A 600px globe at 2px cells is about 70,000 cells per frame at 30 fps, well within budget on a phone; raise `cell` for very large globes. # Dot map A flat world map in dithered dots with pulsing markers and dotted arcs, for customers-worldwide sections. > 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/dot-map.json](https://dithercn.dev/r/dot-map.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/dot-map.json ``` ## Usage [#usage] ```tsx import { DotMap } from "@/components/dithercn/dot-map"; const OFFICES = [ { at: [51.51, -0.13], label: "London" }, { at: [35.68, 139.69], label: "Tokyo" }, ] as const; export function Offices() { return ( ); } ``` The map is an SVG that scales to its container's width and keeps its aspect ratio. Land comes from a 3.8 KB mask of Natural Earth's public-domain land polygons that installs with it as `world-land`, so nothing is fetched at runtime. Each marker pulses and casts a dithered halo on the land around it: dense at the marker, thinning through the Bayer levels. Arcs are dotted curves with a stretch of light running along them. ## Examples [#examples] ### Customers worldwide [#customers-worldwide] Many small markers, round dots and a tight halo, between a headline and a row of stats. ### Region map in a card [#region-map-in-a-card] `bounds` crops the map to a region. Here, Europe, with labelled edge regions in a dashboard card. ### Routes, held still [#routes-held-still] Arcs only, with faint sea dots and `animate={false}`. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `markers` | `{ at: [lat, lon]; label?: string; color?: DitherColor }[]` | `[]` | Locations to mark. Labels show beside the marker and are read by screen readers. | | `arcs` | `{ from: [lat, lon]; to: [lat, lon]; color?: DitherColor }[]` | `[]` | Dotted curves between places. | | `color` | `DitherColor` | `"grey"` | Colour of the land dots. | | `accent` | `DitherColor` | `"ink"` | Colour of markers, halos and arcs that don't set their own. | | `columns` | `number` | `160` | Dots across the full width. | | `shape` | `"square" \| "round"` | `"square"` | Dot shape. | | `halo` | `number` | `14` | Radius in degrees of each marker's dithered glow on the land. 0 turns it off. | | `ocean` | `number` | `0` | Share of sea cells with a faint dot, 0 to 1. | | `bounds` | `{ north; south; west; east }` | `78°N to 56°S, all longitudes` | The region to draw, in degrees. | | `animate` | `boolean` | `true` | Pulse the markers and run light along the arcs. | | `title` | `string` | `"World map"` | Accessible name. Marker labels are appended to it. | ## Notes [#notes] * The map is one image to assistive technology, named by `title` plus the marker labels. * Pulses and running arcs are CSS animations. With reduced motion they are hidden and the map is still. * Pass `markers` and `arcs` as constants or memoised arrays: the dot paths are rebuilt when they change. Land cells are cached per grid size. * Land data: [Natural Earth](https://www.naturalearthdata.com/) 1:50m land, public domain, rasterised to 0.75° cells. # Dither orb A lit, dithered orb that breathes slowly and leans towards the pointer, for AI and assistant surfaces. > 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-orb.json](https://dithercn.dev/r/dither-orb.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/dither-orb.json ``` ## Usage [#usage] ```tsx import { DitherOrb } from "@/components/dithercn/dither-orb"; export function Hero() { return (

Your headline

); } ``` The orb is a sphere shaded by one light, with a soft terminator, a thin rim light on the shadow side and a dithered glow that falls off around it. Its outline breathes with two slow harmonics and a little noise (`morph`). When the pointer moves anywhere on the page, the orb and its light ease towards it, then drift back to the centre when the pointer leaves. Pass `highlight` for a third, brighter tone on the specular spot, such as `"#fff"` or a pale mix of the orb colour. Pick a colour that is light in both themes: `"--foreground"` turns black in light mode. ## Examples [#examples] ### Assistant hero [#assistant-hero] A purple orb with a pale highlight above a prompt. ### Voice status [#voice-status] A 64px orb on a transparent background, with more `morph` and no lean, as a listening indicator. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `color` | `DitherColor` | `"ink"` | Colour of the orb and its glow. | | `highlight` | `DitherColor` | `–` | A third, brighter tone for the specular highlight. | | `background` | `DitherColor` | `"--dither-paper"` | Colour behind the orb. "transparent" floats it over the page. | | `size` | `number` | `0.26` | Radius as a share of the box's shorter side, 0.1 to 0.5. | | `morph` | `number` | `1` | How much the outline wobbles. 0 is a perfect sphere. | | `lean` | `number` | `1` | How far the orb and its light lean towards the pointer. 0 turns it off. | | `glow` | `number` | `0.5` | Strength of the dithered glow around the orb, 0 to 1. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Threshold matrix. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `speed` | `number` | `1` | How fast the outline breathes. | | `animate` | `boolean` | `true` | false draws one still frame and stops following the pointer. | ## Notes [#notes] * The orb is decorative and hidden from assistive technology. * With reduced motion turned on, it draws one still, centred frame and ignores the pointer. * One WebGL context per orb. It pauses offscreen and in hidden tabs, and the pointer listener is passive and eased in one animation frame loop that stops once the orb settles. # Dither fade A dithered edge that fades a section's content into a solid colour, for the seam between two sections or the end of a clipped list. > 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-fade.json](https://dithercn.dev/r/dither-fade.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/dither-fade.json ``` ## Usage [#usage] ```tsx import { DitherFade } from "@/components/dithercn/dither-fade"; export function Changelog() { return (
); } ``` Put the fade inside a `relative` parent. It covers `size` along `side` and paints `color` over whatever is underneath: sparse cells on the content side, solid at the edge. The default colour is `--dither-paper`, your page background. It is still and drawn with CSS masks, so it renders on the server and uses no WebGL. Use as many as you like on one page. ## Examples [#examples] ### Between two sections [#between-two-sections] Give the fade the colour of the section below it, or leave the default when that section uses the page background. The upper section dissolves into the next one with no hard line. ### Both ends of a row [#both-ends-of-a-row] `side="left"` and `side="right"` fade a scrolling row at its ends. Different sizes on each side hint at which way there is more to see. ### Drifting edge [#drifting-edge] With `animate` the edge wanders slowly, drawn with WebGL. Keep it for one hero-sized seam; the still fade is the better default. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `side` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | The edge of the parent the fade sits on. The colour is solid there. | | `size` | `number \| string` | `160` | Depth of the fade. A number is CSS pixels; a string is any CSS length, such as "30%". | | `color` | `DitherColor` | `"--dither-paper"` | The colour the content fades into: a palette name such as "grey", a CSS variable name, or any CSS colour. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | | `animate` | `boolean` | `false` | true lets the edge drift slowly, drawn with WebGL. | | `className` | `string` | `–` | Extra classes for the fade's box. | ## Notes [#notes] * The fade is decorative and hidden from assistive technology. Content under it stays in the page and is still read out, so do not hide anything important beneath it. * The still fade has no motion. With `animate`, reduced motion holds one still frame, and it pauses offscreen like the other backgrounds. * The still fade is 16 masked layers and no script. The animated one costs one small draw per frame and one WebGL context. # Area chart A line over a dithered fill that is densest at the line, for revenue, traffic and other totals over time. > 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/area-chart.json](https://dithercn.dev/r/area-chart.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/area-chart.json ``` ## Usage [#usage] ```tsx import { AreaChart } from "@/components/dithercn/area-chart"; const data = [ { month: "Jan", revenue: 18400 }, { month: "Feb", revenue: 21900 }, { month: "Mar", revenue: 26300 }, ]; export function Revenue() { return ( ); } ``` `data` can hold any objects. `x` names the field with each point's label and every series names the field with its value. Both also accept a function, such as `(datum) => datum.total / 100`, when you render the chart from a Client Component. The chart fills its container's width and redraws when the container resizes. ## Examples [#examples] ### Two layered series [#two-layered-series] With two or more series the chart adds a legend, and every series after the first draws in the next palette colour. Each fill knocks out the ones behind it, so overlapping areas never mix and the lines always sit on top. Scrub with the pointer, or focus the chart and use the arrow keys: a crosshair and readout glide between points. Hover a legend entry to spotlight that series, click it to keep it lit, and press Escape to let go. ### Compact, inside a stat card [#compact-inside-a-stat-card] `axes={false}` drops the axes and the padding around the plot, so the fill can run to the edges of a card. `fill="dotted"` swaps the gradient for a sparse, even lattice of dots. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `data` (required) | `readonly T[]` | | One entry per point along the x axis, in order. | | `x` (required) | `keyof T \| ((datum: T) => string \| number)` | | The field (or function) holding each point's x axis label. | | `series` (required) | `[ChartSeries, ...ChartSeries[]]` | | Each series is { label, value, color? }, where value is a numeric field of T or a function. Later series draw on top. | | `stack` | `"none" \| "stacked" \| "percent"` | `"none"` | none overlaps the series from the baseline. stacked sits each on the ones before it. percent stacks and scales every point to 100%; the readout adds each share. | | `height` | `number` | `240` | Height in px, axes included. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | gradient is densest just under the line and thins toward the axis. dotted is a sparse lattice, hatched diagonal lines, solid flat colour. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | A soft glow in each series' colour behind its fill. Its strength follows --dither-bloom, lower in light mode. | | `colors` | `readonly DitherColor[]` | `ink, orange, green, pink, …` | Colours by series position: palette names such as "green" or any CSS colour. A series' own color wins. | | `interactive` | `boolean` | `true` | Scrubbing, the readout and the legend spotlight. false makes a decorative chart: no focus stop, no readout, just an image with a summary. | | `animate` | `boolean` | `true` | Entrance motion. Always off under reduced motion. | | `level` | `number` | `0.625` | Gradient only: coverage of the densest band, just under the line, from 0 to 1. | | `curve` | `"smooth" \| "linear"` | `"smooth"` | smooth is a monotone curve that never overshoots a data point. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Gradient only: the dither texture. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in px, rounded to whole device pixels. | | `axes` | `boolean` | `true` | Show the value and category axes. | | `tooltip` | `boolean` | `true` | Show the readout while scrubbing. | | `legend` | `boolean` | `Two or more series` | Show the legend under the chart. Its entries spotlight a series on hover and lock it on click. | | `format` | `Intl.NumberFormatOptions \| ((value: number) => string)` | `Compact, as in 1.2K` | How values are written on the axis, in the readout and in the table. Options work from Server Components. | Series colours follow the palette: ink, then orange, green, pink, purple, blue, red and grey. Pass `colors` for the whole chart, or `color` on one series, using a palette name or any CSS colour. ## Notes [#notes] * The chart is a focusable group labelled with a short summary, and the full data sits in a visually hidden table, so screen readers get every value. The arrow keys, Page Up, Page Down, Home and End move the readout without easing, a polite live region reads out the values at each stop, and Escape clears the readout and any locked series. Legend entries are toggle buttons. * On entrance the line draws in and the fill steps up through the dither levels. With reduced motion the finished chart appears at once. * Cells are snapped to whole device pixels, so the dither stays sharp on any screen. Markers use a ring in `--dither-paper`; set it to your card's background if the card is not the page colour. * It is plain SVG with no chart library. The entrance re-renders the chart about 16 times. Scrubbing updates at most once a frame and only moves the crosshair, markers and readout, by transform, so a year of daily points in three series stays smooth. # Bar chart Dithered bars with a solid cap, grouped or stacked, for weekly counts and period comparisons. > 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/bar-chart.json](https://dithercn.dev/r/bar-chart.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/bar-chart.json ``` ## Usage [#usage] ```tsx import { BarChart } from "@/components/dithercn/bar-chart"; const data = [ { week: "W36", deploys: 61 }, { week: "W37", deploys: 68 }, { week: "W38", deploys: 74 }, ]; export function Deploys() { return ( ); } ``` Each bar is a dithered body under a solid 2px cap. One bar is drawn solid, the last by default, with its value above it. When the pointer or keyboard focus moves to another bar, that one fills in instead. `x` and each series' `value` name fields of your data, or take a function when you render from a Client Component. ## Examples [#examples] ### Stacked [#stacked] `layout="stacked"` puts the series on top of each other with a 2px gap between segments, and the axis covers the totals. `highlight="max"` picks the tallest stack. ### Grouped comparison [#grouped-comparison] Grouped bars sit side by side, one per series. `highlight="none"` leaves every bar dithered until you scrub to one, and `fill="hatched"` gives each bar diagonal lines instead of the gradient. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `data` (required) | `readonly T[]` | | One entry per bar or group of bars, in order. | | `x` (required) | `keyof T \| ((datum: T) => string \| number)` | | The field (or function) holding each bar's category label. | | `series` (required) | `[ChartSeries, ...ChartSeries[]]` | | Each series is { label, value, color? }, where value is a numeric field of T or a function. | | `layout` | `"grouped" \| "stacked" \| "percent"` | `"grouped"` | How several series share a category: side by side, on top of each other, or stacked and scaled to 100%. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | gradient is densest at the top of each bar and thins toward the axis. dotted is a sparse lattice, hatched diagonal lines, solid flat colour. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | A soft glow in each series' colour behind its fill. Its strength follows --dither-bloom, lower in light mode. | | `colors` | `readonly DitherColor[]` | `ink, orange, green, pink, …` | Colours by series position: palette names such as "green" or any CSS colour. A series' own color wins. | | `interactive` | `boolean` | `true` | Scrubbing, the readout and the legend spotlight. false makes a decorative chart: no focus stop, no readout, just an image with a summary. | | `animate` | `boolean` | `true` | Entrance motion. Always off under reduced motion. | | `highlight` | `number \| "last" \| "max" \| "none"` | `"last"` | The bar drawn solid, with its value, while nothing is scrubbed. | | `height` | `number` | `240` | Height in px, axes included. | | `level` | `number` | `0.75` | Gradient only: coverage at the top of each bar, from 0 to 1. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Gradient only: the dither texture. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in px, rounded to whole device pixels. | | `axes` | `boolean` | `true` | Show the value and category axes. | | `tooltip` | `boolean` | `true` | Show the readout while scrubbing. | | `legend` | `boolean` | `Two or more series` | Show the legend under the chart. Its entries spotlight a series on hover and lock it on click. | | `format` | `Intl.NumberFormatOptions \| ((value: number) => string)` | `Compact, as in 1.2K` | How values are written on the axis, in the readout and in the table. | Series colours follow the palette: ink, then orange, green, pink, purple, blue, red and grey. Pass `colors`, or `color` on one series, with a palette name or any CSS colour. ## Notes [#notes] * The chart is a focusable group with a short summary, and every value is in a visually hidden table. The arrow keys, Home and End move between bars, a polite live region reads out each stop, and Escape clears the readout and any locked series. The scrubbed column gets a soft band and fills in solid. * Bars rise from the baseline one after another on entrance. With reduced motion they appear in place. * Bars are capped at 36px wide and snapped to whole pixels, and the dither cell to whole device pixels, so edges and cells stay sharp. # Line chart Crisp lines with dithered range bands and point markers, for latency, conversion and period-over-period comparisons. > 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/line-chart.json](https://dithercn.dev/r/line-chart.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/line-chart.json ``` ## Usage [#usage] ```tsx import { LineChart } from "@/components/dithercn/line-chart"; const data = [ { time: "12:00", p10: 160, p50: 188, p90: 238 }, { time: "12:30", p10: 164, p50: 184, p90: 230 }, { time: "13:00", p10: 167, p50: 191, p90: 245 }, ]; export function Latency() { return ( ); } ``` Each series is a 2px line. Give it a `range` and the space between `low` and `high` fills with a light dither, which suits percentiles, forecasts and min/max envelopes. The readout lists the value with its range. ## Examples [#examples] ### This week against last week [#this-week-against-last-week] A `dashed` series reads as a reference: a previous period or a target. The legend repeats the dash, so the two lines never rely on colour alone. Here this week's `range` runs from last week's value to its own, so the dither fills exactly the gap between the two. ### Few points, every one marked [#few-points-every-one-marked] With sparse data, `markers="all"` puts a dot on each point and `curve="linear"` keeps straight segments between them, so nobody reads a curve the data does not have. The band is a sector benchmark in its own `color`, and `zero` starts the axis at 0 because a score's distance from zero matters here. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `data` (required) | `readonly T[]` | | One entry per point along the x axis, in order. | | `x` (required) | `keyof T \| ((datum: T) => string \| number)` | | The field (or function) holding each point's x axis label. | | `series` (required) | `[LineSeries, ...LineSeries[]]` | | Each series is { label, value, color?, range?: { low, high, color? }, dashed? }. value, low and high are numeric fields of T or functions. | | `markers` | `"all" \| "last" \| "none"` | `"last"` | Which points get a dot. | | `zero` | `boolean` | `false` | Start the value axis at zero. By default it fits the data, so small changes stay visible. | | `height` | `number` | `240` | Height in px, axes included. | | `curve` | `"smooth" \| "linear"` | `"smooth"` | smooth is a monotone curve that never overshoots a data point. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | Texture of the range bands. gradient is one even dither level, set by level; solid draws a translucent band. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | A soft glow in each series' colour behind its fill. Its strength follows --dither-bloom, lower in light mode. | | `colors` | `readonly DitherColor[]` | `ink, orange, green, pink, …` | Colours by series position: palette names such as "green" or any CSS colour. A series' own color wins. | | `interactive` | `boolean` | `true` | Scrubbing, the readout and the legend spotlight. false makes a decorative chart: no focus stop, no readout, just an image with a summary. | | `animate` | `boolean` | `true` | Entrance motion. Always off under reduced motion. | | `level` | `number` | `0.25` | Gradient only: coverage of the range bands, from 0 to 1. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Gradient only: the dither texture of the bands. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in px, rounded to whole device pixels. | | `axes` | `boolean` | `true` | Show the value and category axes. | | `tooltip` | `boolean` | `true` | Show the readout while scrubbing. | | `legend` | `boolean` | `Two or more series` | Show the legend under the chart. Its entries spotlight a series on hover and lock it on click. | | `format` | `Intl.NumberFormatOptions \| ((value: number) => string)` | `Compact, as in 1.2K` | How values are written on the axis, in the readout and in the table. | Series colours follow the palette: ink, then orange, green, pink, purple, blue, red and grey. Pass `colors`, or `color` on one series, with a palette name or any CSS colour. ## Notes [#notes] * The chart is a focusable group with a short summary, and every value, including ranges, is in a visually hidden table. The arrow keys, Home and End move the readout without easing, a polite live region reads out each stop, and Escape clears the readout and any locked series. With `bloom`, each line gets a soft glow in its colour. * On entrance solid lines draw from left to right, dashed lines fade in, and bands step up through the dither levels. With reduced motion the finished chart appears at once. * Markers carry a 2px ring in `--dither-paper` so they stay legible where lines cross. Set `--dither-paper` to your card's background if it differs from the page. # Sparkline A tiny inline trend with a dithered fill and a dot on the latest value, for stat cards and table rows. > 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/sparkline.json](https://dithercn.dev/r/sparkline.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/sparkline.json ``` ## Usage [#usage] ```tsx import { Sparkline } from "@/components/dithercn/sparkline"; export function RevenueTrend() { return ( ); } ``` A sparkline takes a plain array of numbers, oldest first. Its height is a prop and its width follows the element, so set it with `className` or let it fill a table cell. By default the value range fits the data, because a sparkline shows the shape of a trend. Pass the same `domain` to every sparkline in a column when the rows should be compared. `label` is required. Screen readers hear it followed by a summary of the data, such as "Revenue, last 8 weeks: 41 to 54, low 41, high 54". ## Examples [#examples] ### In a table [#in-a-table] Each row's trend sits in its own cell at 24px high. `dot={false}` keeps a column of sparklines quiet. ### Line only, one in ink [#line-only-one-in-ink] `area={false}` drops the fill. Each row shares `domain={[0, 180]}`, so a flat service draws flat instead of stretching its small wobble to full height, and the one spike drawn in ink is the first thing you see. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `data` (required) | `readonly number[]` | | Values in order, oldest first. | | `label` (required) | `string` | | What the trend shows. Starts the accessible summary of the data. | | `height` | `number` | `32` | Height in px. The width follows the element. | | `color` | `DitherColor` | `"ink"` | A palette name such as "green", or any CSS colour. | | `area` | `boolean` | `true` | Fill under the line, densest just below it. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | Texture of the fill. gradient is densest just under the line. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | A soft glow in the line's colour behind it. | | `interactive` | `boolean` | `false` | Scrub with the pointer or arrow keys to read single values in a small label. | | `animate` | `boolean` | `true` | Entrance motion. Always off under reduced motion. | | `dot` | `boolean` | `true` | Mark the latest value with a dot. | | `level` | `number` | `0.5` | Gradient only: coverage of the fill just under the line, from 0 to 1. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The dither texture. | | `cell` | `number` | `--dither-cell minus 1` | Size of one dither cell in px, rounded to whole device pixels. Smaller than other charts so the texture reads at small sizes. | | `domain` | `[min: number, max: number]` | `The data's range` | The value range the height covers. Share one across a column so rows compare. | | `format` | `Intl.NumberFormatOptions` | `Compact, as in 1.2K` | How values are written in the accessible summary. | ## Notes [#notes] * By default the sparkline is a single image to assistive technology, named by `label` and a summary of its first, last, lowest and highest values, with no readout; show the exact number next to it. With `interactive` it becomes a focusable group: the arrow keys move a gliding dot and a polite live region reads each value. * On entrance the line draws in, the fill fades up and the dot appears last. With reduced motion it appears finished. * It runs no animation loop and costs one small SVG, so a table with dozens of rows stays cheap. # Heatmap A calendar or matrix heatmap built from dither cells, where every square steps cleanly from a few dots to solid. > 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/heatmap.json](https://dithercn.dev/r/heatmap.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/heatmap.json ``` ## Usage [#usage] ```tsx import { Heatmap } from "@/components/dithercn/heatmap"; const data = [ { date: "2026-10-10", value: 3 }, { date: "2026-10-11", value: 0 }, { date: "2026-10-12", value: 7 }, ]; export function Deploys() { return ; } ``` Each column is a week and each row a weekday. The grid ends with the latest day in `data` and goes back `weeks` weeks, 53 by default. Days missing from `data` count as 0, and entries for the same day add up. Dates are read in UTC, so a square never moves with the visitor's time zone. Every square is 4×4 dither cells. With the default four `steps` a square is dotted, a checkerboard, a hollow frame or solid, and an empty day is a faint backing square. Each level is a fixed mask rather than a raw threshold, so every square keeps a crisp outline and the levels stay distinct even at 2px cells. The scale goes up to six steps. The grid fills its container. Cells shrink from `cell` down to 2px to fit every week; below that the oldest weeks drop off, so a phone shows the most recent months. Hover a square, or focus the grid and use the arrow keys, for a readout above it. Hover a level in the scale to spotlight every square at that level, and click to keep it lit. ## Examples [#examples] ### A year of activity [#a-year-of-activity] A full year with weeks starting on Monday, in `color="purple"`. On a narrow screen it keeps the latest weeks that fit. ### Weekday by hour [#weekday-by-hour] `mode="matrix"` takes a grid of values with row and column labels instead of dates. Here pushes by weekday and hour, with `cell={4}` for larger squares and `steps={5}`. Column labels thin out when they would collide. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `mode` | `"calendar" \| "matrix"` | `"calendar"` | A day per square, or any grid of values. | | `data` (required) | `{ date: string \| Date; value: number }[] \| number[][]` | | Calendar: values by day, date as "YYYY-MM-DD" or a Date read in UTC. Matrix: data[row][column]. | | `weeks` | `number` | `53` | Calendar only: weeks shown, ending with the latest day. | | `weekStart` | `0 \| 1` | `0` | Calendar only: the first row, 0 for Sunday or 1 for Monday. | | `rows` | `string[]` | | Matrix only, required: row labels, top to bottom. | | `columns` | `string[]` | | Matrix only, required: column labels, left to right. | | `steps` | `number` | `4` | Densities above empty, 2 to 6. The last is solid. | | `max` | `number` | `The largest value` | The value drawn solid. Larger values draw solid too, so one outlier doesn't wash out the rest. | | `unit` | `string \| { one: string; other: string }` | | What a value counts, for the readout and summary: "min" or { one: "deploy", other: "deploys" }. | | `color` | `DitherColor` | `"ink"` | Ink colour, a palette name or any CSS colour. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | Soft glow behind the two densest levels. | | `cell` | `number` | `--dither-cell` | Largest dither cell in CSS pixels. Shrinks to 2 to fit the container. | | `legend` | `boolean` | `true` | Show the Less to More scale. | | `interactive` | `boolean` | `true` | Readout, keyboard and the scale spotlight. false draws a static image. | | `animate` | `boolean` | `true` | Squares ripple in from the top left on first view. | ## Notes [#notes] * The grid is a focusable group labelled with a summary: the range, the total and the busiest square. Arrow keys move a square at a time (left and right move a week in calendar mode), Home and End jump to the ends, and a live region reads each square. Each step in the scale is a toggle button with its range as its name. * The ripple plays once when the grid scrolls into view. With reduced motion the grid appears finished and the readout moves without easing. * It is one SVG: a backing path and one path per level, built from whole cells snapped to device pixels. Hover only moves an outline and the readout by transform. # Donut chart A donut or pie chart drawn in dither cells, with a colour and texture per slice and a centre that follows the pointer. > 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/donut-chart.json](https://dithercn.dev/r/donut-chart.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/donut-chart.json ``` ## Usage [#usage] ```tsx import { DonutChart } from "@/components/dithercn/donut-chart"; const data = [ { label: "North America", value: 48210 }, { label: "Europe", value: 31870 }, { label: "Asia Pacific", value: 17430 }, ]; export function Regions() { return ; } ``` Slices run clockwise from 12 o'clock in the order you pass them. Each one draws in the next palette colour (ink, orange, green, pink, purple, blue, red, grey) unless you set `colors` for the chart or `color` on the slice. Every slice is a block of whole dither cells with a solid rim along the outside. `fill="gradient"` thins out from that rim toward the centre; `dotted`, `hatched` and `solid` fill evenly. The gaps between slices are a constant width in cells, so they stay crisp in both modes. Hover a slice or its legend row to spotlight it: the others fade, the slice steps out by two cells, and the centre switches from the total to that slice's value and share. Click to keep it lit and press Escape to let go. Focus the chart with Tab and the arrow keys step through the slices. ## Examples [#examples] ### Storage card [#storage-card] A smaller ring with `fill="hatched"`. The free space is its own slice with `color: "grey"` and `fill: "dotted"`, so it reads as empty without a second chart. ### Pie in a landing section [#pie-in-a-landing-section] `mode="pie"` drops the hole. The spotlit slice gets a readout card that glides between slices. `bloom="high"` adds a soft glow in each slice's colour. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `data` (required) | `DonutSlice[]` | | { label: string; value: number; color?: DitherColor; fill?: FillVariant }, drawn clockwise from the top. | | `label` (required) | `string` | | What the total counts. Shown in the centre and used in the chart's summary. | | `mode` | `"donut" \| "pie"` | `"donut"` | A ring with the total in the middle, or full wedges with a readout over the active slice. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | Texture of every slice. A slice's own fill wins. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | Soft glow in each slice's colour behind it. | | `colors` | `DitherColor[]` | | Colours by slice position, as palette names or CSS colours. A slice's own color wins. | | `size` | `number` | `220` | Diameter in CSS pixels. | | `thickness` | `number` | `0.36` | Donut only: ring thickness as a share of the radius. | | `gap` | `number` | `1` | Gap between slices, in dither cells. | | `unit` | `string` | `""` | Shown after every value, such as " GB". | | `format` | `Intl.NumberFormatOptions \| ((value: number) => string)` | `{ maximumFractionDigits: 0 }` | How values and the total are written. | | `legend` | `boolean` | `true` | Show the legend. When false it stays available to screen readers. | | `interactive` | `boolean` | `true` | Hover, keyboard focus and the legend spotlight. false draws a static image. | | `animate` | `boolean` | `true` | Sweep the slices in clockwise the first time the chart is seen. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Threshold pattern of the gradient fill. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | ## Notes [#notes] * The chart is a focusable group with a summary label. Arrow keys, Home and End move between slices, Enter keeps one lit, and a live region reads the active slice. A hidden table lists every value and share. * The sweep plays once when the chart scrolls into view. With reduced motion turned on, slices appear at once and the spotlight has no transition. * The drawing is one SVG path per slice, built from whole cells snapped to device pixels. Hovering only changes opacity and a transform. # Radar chart One or more series across several axes, drawn as polygons of dither cells whose density steps up ring by ring. > 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/radar-chart.json](https://dithercn.dev/r/radar-chart.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/radar-chart.json ``` ## Usage [#usage] ```tsx import { RadarChart } from "@/components/dithercn/radar-chart"; export function Evals() { return ( ); } ``` Axes run clockwise from 12 o'clock. Each series lists one value per axis in the same order. Without `max`, the outer ring sits at a round number above the largest value. Every series is a polygon of whole dither cells with a solid one-cell outline and square markers on its corners. With `fill="gradient"` the density steps up once per ring, thin at the centre and dense near the rim, so the grid reads straight through the shape. Each series takes the next palette colour. Where series overlap their cells interleave, so neither hides the other, and every outline stays on top. Move the pointer around the chart and the readout snaps to the nearest axis, with every series' value there. Focus the chart and the arrow keys step around the axes. Hover a legend entry to spotlight a series, click to keep it lit, and press Escape to let go. The chart fills its container up to `size` and shrinks with it, keeping room for the labels. ## Examples [#examples] ### Single series in a card [#single-series-in-a-card] One series in `green` with `fill="dotted"` and a round grid. The legend hides itself when there is only one series. ### Comparing plans [#comparing-plans] Three series on seven axes with `fill="hatched"`, five rings and `bloom="high"`. Hatching keeps overlapping shapes readable because each one's lines run between the other's. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `axes` (required) | `[string, ...string[]]` | | Axis labels, clockwise from the top. Three or more read best. | | `series` (required) | `[RadarSeries, ...RadarSeries[]]` | | { label: string; values: number[]; color?: DitherColor; fill?: FillVariant }, one value per axis. | | `max` | `number` | `A round number above the largest value` | The value at the outer ring. | | `rings` | `number` | `4` | Grid rings. They also set the steps of the gradient fill. | | `grid` | `"polygon" \| "circle"` | `"polygon"` | Shape of the grid rings. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | Texture of every series. A series' own fill wins. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | Soft glow in each series' colour behind it. | | `colors` | `DitherColor[]` | | Colours by series position. A series' own color wins. | | `size` | `number` | `360` | Largest width and height in CSS pixels. It shrinks to fit its container. | | `format` | `Intl.NumberFormatOptions \| ((value: number) => string)` | `Compact, as in 1.2K` | How values are written in the readout and the data table. | | `legend` | `boolean` | `With two or more series` | Show the legend. | | `interactive` | `boolean` | `true` | The axis scrub, keyboard and legend spotlight. false draws a static image. | | `animate` | `boolean` | `true` | Grow out from the centre the first time the chart is seen. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Threshold pattern of the gradient fill. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | ## Notes [#notes] * The chart is a focusable group with a summary label. The arrow keys move between axes, a live region reads each axis's values, and a hidden table lists every value. * The shapes grow out from the centre once when the chart scrolls into view. With reduced motion they appear at once and the readout moves without easing. * The drawing is one SVG path per series, built from whole cells snapped to device pixels. Scrubbing only moves the readout by transform and restyles one spoke. # Gauge A radial gauge of dithered blocks for a single reading, such as load, usage or a score, with a solid block at the value. > 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/gauge.json](https://dithercn.dev/r/gauge.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/gauge.json ``` ## Usage [#usage] ```tsx import { Gauge } from "@/components/dithercn/gauge"; export function CpuGauge({ load }: { load: number }) { return ; } ``` The arc is split into blocks over a faint track in the same colour. Blocks up to the value fill in, and the block at the value is solid and steps out by one cell, so the arc points at the reading like a needle. With `fill="gradient"` each block behind it is a step lighter; `dotted`, `hatched` and `solid` fill evenly. ## Examples [#examples] ### Dials on a host card [#dials-on-a-host-card] `variant="dial"` sweeps 270°, like a speedometer. A gauge over 90% switches to `color="red"` with `fill="hatched"` and a low bloom, so it reads as a warning without relying on colour alone. ### Ring beside a summary [#ring-beside-a-summary] `variant="ring"` is a full circle starting at the top, with the reading in the middle, here in `green` with `fill="dotted"`. It hides the min and max labels, which would sit on top of each other. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `value` (required) | `number` | | The reading. Values outside min and max are clamped. | | `label` (required) | `string` | | What is measured. Shown under the value and used as the accessible name. | | `min` | `number` | `0` | The start of the scale. | | `max` | `number` | `100` | The end of the scale. | | `variant` | `"half" \| "dial" \| "ring"` | `"half"` | A 180° arc, a 270° dial, or a full ring. | | `size` | `number` | `220` | Width in CSS pixels. Text and ring thickness scale with it. | | `segments` | `number` | `28, 36 or 48` | How many blocks the arc is split into. The default depends on the variant. | | `unit` | `string` | `""` | Shown after the value, such as "%". | | `format` | `Intl.NumberFormatOptions` | `{ maximumFractionDigits: 1 }` | How the value, min and max are formatted. | | `showRange` | `boolean` | `true, false for ring` | Show min and max under the ends of the arc. | | `color` | `DitherColor` | `"ink"` | Colour of the filled blocks, a palette name or any CSS colour. | | `trackColor` | `DitherColor` | `color` | Colour of the empty track, drawn faint. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | Texture of the filled blocks. The block at the value is always solid. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | Soft glow in the fill's colour behind the filled blocks. | | `animate` | `boolean` | `true` | Fill the blocks in one by one on mount. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Threshold pattern of the gradient fill. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. The ring thickness snaps to whole cells. | ## Notes [#notes] * The gauge has `role="meter"` with `aria-valuenow`, `aria-valuemin`, `aria-valuemax` and an `aria-valuetext` that includes the unit. The drawing is hidden from assistive technology. * Blocks fade in one after another when the gauge appears or the value rises. With reduced motion turned on they appear at once. * It is plain SVG with one pattern per density, so it costs nothing after it renders. Bloom is a blurred copy of the filled blocks. # Progress bar A progress bar built from dither cells: any fill texture with a solid leading edge, stacked parts, step segments and an indeterminate comet. > 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/progress-bar.json](https://dithercn.dev/r/progress-bar.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/progress-bar.json ``` ## Usage [#usage] ```tsx import { ProgressBar } from "@/components/dithercn/progress-bar"; export function Upload({ percent }: { percent: number }) { return ; } ``` `value` runs from 0 to `max`, which is 100 unless you set it. The fill sits on a faint track in the same colour. With `fill="gradient"` it is solid at the leading edge and thins out behind it; `dotted`, `hatched` and `solid` fill evenly. Every texture ends in a solid one-cell cap, so the exact value is always readable. The fill grows in on mount and eases between values. Leave `value` out when you can't measure progress. The bar becomes indeterminate: a comet with a dense head and a dithered tail sweeps across the track. ```tsx ``` ## Examples [#examples] ### Usage card [#usage-card] `parts` stacks several values in one bar, each in its own colour, with a legend underneath. Here storage is split by file type. Build minutes are close to the limit, so that bar uses `color="red"`, `fill="hatched"` and a low bloom. ### Upload list [#upload-list] Three states at `size="lg"`: a finished file in solid green, one in progress, and an indeterminate step while the server transcodes. ### Onboarding steps [#onboarding-steps] `segments` cuts the bar into blocks a cell apart. With one block per task, three of five tasks fill three blocks. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `label` (required) | `string` | | What is in progress. Shown above the bar and used as its accessible name. | | `value` | `number \| null` | | Progress from 0 to max. Leave it out for an indeterminate bar. | | `parts` | `ProgressPart[]` | | { label: string; value: number; color?: DitherColor; fill?: FillVariant }, stacked left to right instead of value. | | `unit` | `string` | | With parts: shown after each value in the legend, such as " GB". | | `max` | `number` | `100` | The value that means done, or the capacity for parts. | | `valueText` | `string` | `The percentage` | Text for the readout and for screen readers. | | `hideLabel` | `boolean` | `false` | Hide the label and readout visually. Screen readers still get them. | | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Bar height: 2, 4 or 6 cells. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | Texture of the fill. Gradient is densest at the leading edge. | | `color` | `DitherColor` | `"ink"` | Colour of the fill and its track, a palette name or any CSS colour. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | Soft glow in the fill's colour. | | `segments` | `number` | | Cut the bar into this many blocks a cell apart, for step meters. | | `animate` | `boolean` | `true` | Grow in on mount and ease between values. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | ## Notes [#notes] * The root has `role="progressbar"`, is named by the label and reports the value (or `valueText`). An indeterminate bar leaves out the value, as assistive technology expects. * Width changes ease over 600ms; the comet moves only by transform. With reduced motion the fill jumps to its value and the comet rests in the middle of the track. * It is plain HTML with CSS masks: no SVG, canvas or animation loop. # Uptime bar A status-page row of daily bars, told apart by colour and texture, with details for each day. > 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/uptime-bar.json](https://dithercn.dev/r/uptime-bar.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/uptime-bar.json ``` ## Usage [#usage] ```tsx import { UptimeBar } from "@/components/dithercn/uptime-bar"; const days = [ { date: "2026-10-11", uptime: 1 }, { date: "2026-10-12", uptime: 0.9962, note: "Elevated latency on writes." }, { date: "2026-10-13", uptime: 1 }, ]; export function ApiStatus() { return ; } ``` Pass one entry per day, oldest first. Each day's status comes from its `uptime`: 99.9% or more is up, 99% or more is degraded, and anything lower is an outage. Set `status` on a day to override that, or `uptime: null` for a day with no data. Statuses differ in texture as well as colour. Up days use `fill` (a gradient that is dense at the base by default) in green, degraded days are hatched orange, outages are solid red and days without data are faint grey dots. `bloom` puts a soft glow behind degraded and down days only, so incidents stand out from a distance. The row shows the last 30 days on narrow screens, 60 from 28rem wide and 90 from 42rem, based on the width of its container. The percentage on the right is the average over every day with data. ## Examples [#examples] ### Status page [#status-page] Several rows in one card. The Realtime service is newer than the window, so its early days have no data and show as faint dots. ### Your own colours [#your-own-colours] `colors` overrides the colour of any status with a palette name or any CSS colour. This one draws up days as blue dots and degraded days in purple, with 3px cells and a high bloom on incidents. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `label` (required) | `string` | | The service name. | | `days` (required) | `UptimeDay[]` | | { date: string; uptime: number \| null; status?: UptimeStatus; note?: string }, oldest first. date is an ISO date. | | `colors` | `Partial>` | `green, orange, red, grey` | Colours for "up", "degraded", "down" and "none", as palette names or CSS colours. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | Texture of the up days. Degraded days are hatched, outages solid and days without data dotted. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | Soft glow behind degraded and down days. | | `locale` | `string` | `"en-US"` | Locale for the dates in tooltips. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Threshold pattern of the gradient fill. | | `cell` | `number` | `2` | Size of one dither cell in CSS pixels. Small, so narrow bars still show a pattern. | ## Notes [#notes] * Status shows in texture as well as colour: up days use the fill, degraded days are hatched, outages solid and days without data dotted. The statuses stay apart for colour-blind readers and in greyscale. * Each bar is a button with a full description, such as "Oct 9, 2026, Degraded, 99.62% uptime". Only one bar is in the tab order; the arrow keys, Home and End move between days, and Escape closes the tooltip. Tapping a bar shows its tooltip on touch screens. * Dates are formatted in UTC, so the server and the browser agree. # Stat card A KPI card with a headline number, a coloured delta and a dithered trend along the bottom. > 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/stat-card.json](https://dithercn.dev/r/stat-card.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/stat-card.json ``` ## Usage [#usage] ```tsx import { StatCard } from "@/components/dithercn/stat-card"; export function Revenue() { return ( ); } ``` `delta` is a percentage: `12.4` reads as +12.4%. It is green when it moves the `good` way and red when it doesn't, using the palette's green and red pulled slightly toward the text colour so the small text keeps its contrast. `trend` takes recent values, oldest first, and draws them as a line with a square marker on the latest value. Under it, `fill="gradient"` draws bands that thin out below the line in dither steps; `dotted`, `hatched` and `solid` fill the area evenly. `color` takes a palette name such as `"blue"`, and `bloom` adds a soft glow behind the trend. The card uses `bg-card` and a faint dot surface in the ink colour that fades out from the top right corner. Pass a `className` to change its background or width. ## Examples [#examples] ### KPI row [#kpi-row] Four cards in a grid. Latency and error rate set `good="down"`, so latency going up is red and error rate going down is green. Each card has its own `color`, and latency, the one moving the wrong way, uses `color="red"` with `fill="hatched"` and a low bloom. ### Without a trend [#without-a-trend] Leave `trend` out for a compact card, and `delta` out when there is nothing to compare. A delta of 0 is grey with no arrow. ## Props [#props] | Prop | Type | Default | Description | | --- | --- | --- | --- | | `label` (required) | `string` | | What the number is. | | `value` (required) | `number \| string` | | The headline number. Numbers get thousands separators; strings show as given. | | `delta` | `number` | `–` | Change since the last period, in percent. | | `deltaLabel` | `string` | `–` | What the change compares with, such as "vs last month". | | `good` | `"up" \| "down"` | `"up"` | Which direction counts as good, for the delta colour. | | `trend` | `number[]` | `–` | Recent values, oldest first. Needs at least two. | | `color` | `DitherColor` | `"ink"` | Colour of the trend and the dot surface, a palette name or any CSS colour. | | `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | Texture under the trend line. | | `bloom` | `"off" \| "low" \| "high"` | `"off"` | Soft glow in the colour behind the trend. | | `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Threshold pattern of the gradient fill. | | `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. | ## Notes [#notes] * Screen readers hear the label, the value and the delta in words, such as "Up 12.4% vs last month". The trend and the surface are decorative and hidden. * The trend is drawn at the card's real width in CSS pixels, so cells stay square and crisp at any size. It redraws when the card resizes. * Nothing moves, so there is nothing to reduce. # Dither button A button with a lit, dithered surface: a highlight dithered down from the top, a shade dithered up from the bottom, brighter on hover and pressed in on click. > 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-button.json](https://dithercn.dev/r/dither-button.json), under `files[].content`. ## Installation [#installation] ```bash npx shadcn@latest add https://dithercn.dev/r/dither-button.json ``` ## Usage [#usage] ```tsx import { DitherButton } from "@/components/dithercn/dither-button"; export function Actions() { return ( <> Deploy View logs ); } ``` `DitherButton` renders a real `