KQ-1386 · DITHER/CURTAIN
Dither Curtain
A veil of ordered dots over any content: where it is partly drawn back, cells switch on and off by a Bayer threshold, and the dots coarsen through nested grids toward the middle of the dissolving edge, so the content appears through a regular screen rather than a blur. The reveal is a tween across a ragged front (a dissolve is a clip, never a spring) and covering again is a shorter exit tween; while veiled, a mouse or pen pushes a dithered hole through it that trails the pointer and opens on springs.glide. With the hover trigger the reveal grows from the pointer, and keyboard focus anywhere inside lifts it the same way.
Basinworks · private beta
The quiet water report
Readings from every basin, sent at dawn.
veiled · move over it to peek
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/dither-curtain
Props
- children
- ReactNode
- default —
- The content behind the veil; it stays fully interactive and in the accessibility tree throughout.
- matrix
- "2x2" | "4x4" | "8x8"
- default "4x4"
- The ordered-dither matrix: 2×2 is a coarse check, 8×8 a fine, even screen.
- scale
- number
- default 4
- The finest dot, in CSS pixels.
- coarsen
- number
- default 0.6
- How much the dots coarsen toward the middle of the dissolving edge, from one size at 0 to up to 8× scale at 1.
- shape
- "square" | "round"
- default "square"
- Square cells, or round dots that grow until a full veil is solid.
- color
- string
- default "var(--accent)"
- The veil's colour, resolved on the element so it follows the theme.
- trigger
- "mount" | "inView" | "hover" | "manual"
- default "inView"
- When the veil draws back: on mount, when scrolled into view, on hover or keyboard focus, or when revealed turns true.
- revealed
- boolean
- default —
- For trigger="manual": true draws the veil back, false brings it over again.
- once
- boolean
- default true
- Reveal only once; false lets the veil come back when the piece leaves the view (inView) or the pointer and focus leave (hover).
- origin
- "left" | "right" | "top" | "bottom" | "center" | "pointer"
- default "left" ("pointer" with the hover trigger)
- Where the dissolve starts.
- duration
- number
- default 900
- How long the reveal takes, ms; covering again takes 0.6× as long.
- delay
- number
- default 120
- Wait before a mount or in-view reveal, ms.
- softness
- number
- default 0.4
- How wide the dithered band at the edge is, 0 to 1.
- ragged
- number
- default 0.4
- How ragged the edge is, from a straight line at 0 to 1.
- holeRadius
- number
- default 64
- The radius of the hole a mouse or pen pushes through the veil, px; 0 turns it off.
- interactive
- boolean
- default true
- The pointer pushes a hole through the veil (mouse and pen only), and with the hover trigger the wrapper takes keyboard focus.
- seed
- number
- default 11
- Seed for the edge's raggedness.
- dpr
- number
- default 2
- The highest device pixel ratio the canvas draws at (never above 2).
- onRevealChange
- (revealed: boolean) => void
- default —
- Fires when a reveal or a re-cover completes, with whether the content is now revealed.
- className
- string
- default —
- Classes for the wrapper; give it the content's radius so the veil follows it.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | The content behind the veil; it stays fully interactive and in the accessibility tree throughout. |
| matrix | "2x2" | "4x4" | "8x8" | "4x4" | The ordered-dither matrix: 2×2 is a coarse check, 8×8 a fine, even screen. |
| scale | number | 4 | The finest dot, in CSS pixels. |
| coarsen | number | 0.6 | How much the dots coarsen toward the middle of the dissolving edge, from one size at 0 to up to 8× scale at 1. |
| shape | "square" | "round" | "square" | Square cells, or round dots that grow until a full veil is solid. |
| color | string | "var(--accent)" | The veil's colour, resolved on the element so it follows the theme. |
| trigger | "mount" | "inView" | "hover" | "manual" | "inView" | When the veil draws back: on mount, when scrolled into view, on hover or keyboard focus, or when revealed turns true. |
| revealed | boolean | — | For trigger="manual": true draws the veil back, false brings it over again. |
| once | boolean | true | Reveal only once; false lets the veil come back when the piece leaves the view (inView) or the pointer and focus leave (hover). |
| origin | "left" | "right" | "top" | "bottom" | "center" | "pointer" | "left" ("pointer" with the hover trigger) | Where the dissolve starts. |
| duration | number | 900 | How long the reveal takes, ms; covering again takes 0.6× as long. |
| delay | number | 120 | Wait before a mount or in-view reveal, ms. |
| softness | number | 0.4 | How wide the dithered band at the edge is, 0 to 1. |
| ragged | number | 0.4 | How ragged the edge is, from a straight line at 0 to 1. |
| holeRadius | number | 64 | The radius of the hole a mouse or pen pushes through the veil, px; 0 turns it off. |
| interactive | boolean | true | The pointer pushes a hole through the veil (mouse and pen only), and with the hover trigger the wrapper takes keyboard focus. |
| seed | number | 11 | Seed for the edge's raggedness. |
| dpr | number | 2 | The highest device pixel ratio the canvas draws at (never above 2). |
| onRevealChange | (revealed: boolean) => void | — | Fires when a reveal or a re-cover completes, with whether the content is now revealed. |
| className | string | — | Classes for the wrapper; give it the content's radius so the veil follows it. |