# 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).

<ComponentPreview src="examples/dither-button-demo.tsx" size="sm" controls="ditherButtonControls">
  <DitherButtonDemo />
</ComponentPreview>

## Installation [#installation]

<InstallBlock name="dither-button" />

## Usage [#usage]

```tsx
import { DitherButton } from "@/components/dithercn/dither-button";

export function Actions() {
  return (
    <>
      <DitherButton color="blue">Deploy</DitherButton>
      <DitherButton variant="outline" fill="dotted">
        View logs
      </DitherButton>
    </>
  );
}
```

`DitherButton` renders a real `<button>` with `type="button"` unless you pass another `type`. A filled button is lit like a pixel-art bevel: the full colour, with a highlight (the colour mixed towards white) dithered down from the top and a shade (mixed towards black) dithered up from the bottom, so the Bayer steps show at rest. Hover fades in a longer, denser highlight. Press fades in a shade from the top, so the face looks pushed in. Only opacity animates, so the cells stay on the grid.

`outline` buttons are a faint tint inside a hairline edge, with the plain colour dithered up from the bottom. Hover and press make that dither denser and taller, which suits secondary actions.

The label is dark on the bright dark-theme colours and light on the deeper light-theme ones.

## Examples [#examples]

### Fills and colours [#fills-and-colours]

Every fill in four colours, filled and outline. `fill` sets the texture of the shade: `gradient` is a Bayer ramp up from the bottom, `dotted` a staggered dot lattice, `hatched` rising diagonals, and `solid` a thin dithered rim along the bottom edge. The top highlight is the same on all four.

<ComponentPreview src="examples/dither-button-palette.tsx" size="md">
  <DitherButtonPalette />
</ComponentPreview>

### Loading in a form [#loading-in-a-form]

`loading` keeps the label and the button's width, steps three cells before the label, pulses the hover highlight, and sets `aria-busy`. Clicks are ignored until it clears. Submit this one to see it.

<ComponentPreview src="examples/dither-button-form.tsx" size="md">
  <DitherButtonForm />
</ComponentPreview>

### As a link [#as-a-link]

Pass `asChild` and a single child, such as Next.js `Link` or a plain `<a>`, and the child gets the button's look and fill. It stays a link: right click and middle click work, and the router handles navigation.

<ComponentPreview src="examples/dither-button-link.tsx" size="md">
  <DitherButtonLink />
</ComponentPreview>

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"filled" \| "outline"` | `"filled"` | Filled is the lit colour surface. Outline is a faint tint whose dither grows on hover. |
| `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | Texture of the shade: a Bayer ramp, a dot lattice, diagonals, or a thin rim. |
| `color` | `DitherColor` | `"ink"` | A palette name such as "blue" or "green", a CSS variable name, or any CSS colour. |
| `size` | `"sm" \| "md" \| "lg" \| "icon"` | `"md"` | Heights of 32, 36 and 44 pixels. icon is a 36 pixel square for a lone icon; give it an aria-label. |
| `bloom` | `"off" \| "low" \| "high"` | `"off"` | A soft glow in the button's colour that follows the cells. |
| `cell` | `number` | `2` | Size of one dither cell in CSS pixels. |
| `loading` | `boolean` | `false` | Busy state: keeps the label, ignores clicks and sets aria-busy. |
| `asChild` | `boolean` | `false` | Render your own single child, such as a link, with the button's look. |

All other props pass through to the `<button>`, or to your child with `asChild`.

## Notes [#notes]

* Focus shows a 2 pixel outline with `:focus-visible` and the hover highlight, so keyboard users see the same step as pointer users.
* Hover only applies on devices that can hover. Press works everywhere, so touch screens get the pressed-in shade as feedback.
* With reduced motion, the layers switch without fading, the press does not scale, and the loading cells hold still.
* Disabled and loading buttons drop to 45% opacity and a not-allowed or progress cursor. For a disabled link, set `aria-disabled` on it and leave out `href`.
* The fill is plain CSS masks in one shared `<style>` tag. The button only needs client JavaScript for `asChild` and `loading`.