Halftone UI: a component library that ships its own printing press
Most UI libraries paint.
A background is a hex value. A gradient is a CSS function. A chart is an SVG path. The result is clean, predictable, and often a little too familiar.
Halftone UI takes a different route: it prints.
Every visual surface is a live canvas. A component provides a tone function — how dark should the ink be at this point? — and the press keeps the dots that tone can reach.
const meter = surface(canvas, {
tone: (p, W) => (p.x / W < 0.72 ? 0.95 : 0.05),
pattern: "hatch",
})
That threshold operation is the whole idea behind halftone UI. It works for a button, a meter, a chart, a photo, or a full-page example.
A pie chart and an image, both printed by the same engine.
The grain is the motion
The dots come from a seeded Poisson-disk cloud: blue noise, similar to the point distribution used in stochastic screening. Each point carries its own threshold. When the tone changes, the dots re-test themselves.
That makes animation feel different. The UI does not tween a CSS fill from one color to another. The grain itself moves as the press re-evaluates the field.
The seed is deterministic, so a reload produces the same print. Press “reroll” and the whole page reprints from a new seed.
Four screens, one engine
The library ships with four screens:
-
hatch— crosshatch, and the default -
stipple— stochastic/FM screening -
lines— a line screen -
waves— a warped line screen
Every demo has a live screen picker. Change the screen and the character of the page changes, while the underlying tone field stays the same.
There is also a real four-plate press: cyan, magenta, yellow, and key, separated at process screen angles with adjustable misregistration. The slightly imperfect overlap is part of the result.
The same tone-field approach works for charts, not just decorative surfaces.
Copy the code, own the result
Halftone UI is intentionally not an npm package. The source is meant to be copied into your project, in the same spirit as shadcn-style libraries.
The core has no runtime dependencies. React and Vue adapters sit beside it and import the same core engine.
import { HalftoneProvider, Surface } from "./halftone/react/index.js"
export default function App() {
return (
<HalftoneProvider>
<Surface
field={(u, v) => (v > 0.6 ? 1 : 0)}
style={{ height: 160 }}
/>
</HalftoneProvider>
)
}
The adapter keeps the real DOM underneath the ink: buttons are buttons, meters are <progress>, and charts expose tables with captions. The canvas is decoration; the semantics remain inspectable.
There is a docs page for every weird edge
The site contains 88 documented sections: primitives, charts, sign-in flows, dashboards, pricing pages, dialogs, menus, comboboxes, OTP fields, image halftoning, and Smudge — the resident ink imp.
The whole thing is designed to be played with. Open the docs, change the grain, rotate the hue wheel, switch themes, and watch the same press redraw every mounted surface.
Even the mascot is pressed from the same halftone engine.
Try it here:
Halftone UI is MIT licensed. No dependencies. No build step for the standalone version. Just a small press, a seeded cloud of dots, and a tone field waiting to be inked.



Top comments (0)