The React UI component library with controllable states.
English | 简体中文
haze-ui is a React 19 component library styled with Linaria (zero-runtime
CSS-in-JS) against --haze-* design tokens: 148 components in 9 groups,
ESM-only with a tree-shakeable preserveModules dist, RTL by construction,
and an RSC-aware split ('use client' is pre-injected only on interactive
modules, so the static subset renders as React Server Components). Every
stateful component speaks the same ControlOrValue<T> protocol — one prop,
checked?: Control<T> | T, covers controlled and uncontrolled usage.
Only react and react-dom (v19) are peers; every engine (react-f0rm,
TanStack Table, recharts, dnd-kit, qrcode) is a regular dependency that
tree-shakes away when your imports never reach it.
npm i haze-ui
# or
pnpm add haze-uiLoad the CSS — pick one of two modes:
// A) one full stylesheet (simplest)
import 'haze-ui/styles.css';// B) the official Vite plugin injects per-component CSS automatically
// vite.config.ts
import hazeCss from 'vite-plugin-haze-ui';
export default { plugins: [react(), hazeCss()] };Install the plugin with npm i -D vite-plugin-haze-ui.
Then render your first component:
import { lightTheme, Button } from 'haze-ui';
export function App() {
return (
<main className={lightTheme}>
<Button onClick={() => console.log('haze!')}>Start</Button>
</main>
);
}The theme class activates the token set — that is the whole setup. For a template start, painless is a RealWorld SPA template built on haze-ui — clone it and delete what you don't need.
Colors are generated in OKLCH with interaction states derived at runtime via CSS relative colors, so the browser floor is Chrome/Edge 119+, Safari 16.4+ or Firefox 128+ (no HSL/hex fallbacks).
The browserslist field in package.json governs the JS
API surface only: eslint-plugin-compat lints every src/**/*.{ts,tsx}
file against those targets (chrome >= 119, edge >= 119, safari >= 16.4,
firefox >= 128), so a modern JS API below that floor fails CI rather than
shipping. CSS is intentionally not transpiled: OKLCH colors, CSS relative
colors, the popover attribute and CSS anchor positioning are emitted as-is
with no polyfills or fallbacks — the same four engines are the hard CSS floor.
- React 19 only, by design — ref-as-prop, native
popover/anchor-positioning overlays and the RSC-aware'use client'split build on React 19's shapes; there is no React 18 compatibility layer (peer rangereact: ^19.0.0). → Getting started - Accessibility as a release gate — an axe case in every component test suite, Playwright e2e across Chromium/Firefox/WebKit plus a phone-viewport project, pixel-locked visual baselines, and a real VoiceOver smoke suite on macOS CI. → Accessibility guide · A11y contracts
- Per-component CSS — the JS entry imports no stylesheet. Load the full
haze-ui/styles.css, cherry-pickhaze-ui/css/*.csssubpaths, or let vite-plugin-haze-ui inject the right files per import throughhaze-ui/css-manifest.json. → Getting started Control<T> | Tcontrollable state — one state prop per component, powered by react-use-control; nodefaultXxx/xxxdual API to keep in sync. → Getting started- Curated icons, zero cost —
haze-ui/iconsre-exports the ~100 most common lucide-react glyphs to pair with theIconcomponent, plus the matchinghazeIconSizescale;lucide-reactis a regular dependency withsideEffects: false, so unused icons — or the whole set — tree-shake away. - AI / chat component family —
ChatContainer,ChatMessage,StreamingText,ToolCallCard,ThinkingIndicatorandChatInputcover the agent chat surface;Plan,Queue,InlineCitationand the collapsibleStepTimelinetrace rows cover the agentic-UI patterns, with a unit-tested adapter recipe for the Vercel AI SDK'suseChat. → AI showcase · Recipes - React Compiler verified — the whole suite runs green with
babel-plugin-react-compiler enabled (
pnpm test:compiler): auto-memoization is part of the release gate, not a hope. - Runtime benchmarks —
pnpm bench(vitest bench, jsdom) measures pure JS-side costs of the heaviest components — 1000-row DataTable render and sort flips, a 5000-item VirtualList scroll window, and Select panel open/close cycles. Informational numbers for trend spotting; CI runs the job non-blocking. - Codemod upgrade toolchain —
pnpm codemod <transform> <path>runs the jscodeshift-powered migrations in scripts/codemods; every future major ships a codemod for its breaking changes. - Native-form ready — value-holding components (Select, Switch, Rating,
Slider, TagInput, OTPInput, Segmented, ColorPicker, RichTextEditor) accept
a
nameand submit intoFormData, so<form action={serverAction}>works without a form library; react-f0rm integration stays first-class. - Multi-calendar date layer — Gregorian plus six built-in adapters
(Hijri Umm al-Qura, Persian, Hebrew, Japanese era, Buddhist, Ethiopic):
integer-arithmetic implementations cross-verified against full-ICU
Intloracles, exported from the barrel viagetAdapter(). → Components - OKLCH tokens with W3C DTF export — hover/active/focus-ring colors re-derive at runtime from CSS relative-color formulas, and the whole token registry exports to the W3C Design Tokens format for Figma / Tokens Studio. → Tokens · Theme editor
| Resource | Link |
|---|---|
| Documentation | https://wmzy.github.io/haze-ui/ |
| Getting started | https://wmzy.github.io/haze-ui/getting-started |
| Migration guide (AntD / shadcn) | https://wmzy.github.io/haze-ui/guides/migration |
| llms.txt / full API for AI readers | https://wmzy.github.io/haze-ui/llms.txt |
MCP server — ships as a bin of this package (npx haze-ui-mcp) |
https://wmzy.github.io/haze-ui/help |
| shadcn registry | registry/README.md — pnpm dlx shadcn@latest add wmzy/haze-ui/button |
| painless (RealWorld SPA template) | https://github.com/wmzy/painless — GitHub |
| vite-plugin-haze-ui (CSS auto-injection) | https://www.npmjs.com/package/vite-plugin-haze-ui — npm |
Every styled export is also installable through the shadcn CLI as a thin wrapper over the npm package — see registry/README.md.
| Channel | Install | Version shape | Who it's for |
|---|---|---|---|
stable (latest) |
npm i haze-ui |
1.34.1 |
Production. Cut from main by semantic-release whenever a relevant commit (feat/fix/perf…) lands. |
| beta | npm i haze-ui@beta |
1.35.0-beta.1 |
Trying upcoming changes early. Cut from the beta branch through semantic-release's prerelease flow — same commit analysis, changelog and GitHub Release as stable, published to the beta dist-tag so latest never moves. |
| canary | npm i haze-ui@canary |
0.0.0-canary.a1b2c3d4.123 |
Verifying a fix against one exact commit. Manually dispatched from any ref, stamped with the commit SHA + run number, and fully bypasses semantic-release — no commit analysis, no changelog, no git tag. |
Beta vs canary in one line: beta is an ordered prerelease line (x.y.z-beta.n) that graduates to stable when its commits merge into main; canary is a disposable per-commit snapshot (0.0.0-canary.<sha>.<run>) that the next dispatch supersedes and nothing ever promotes.
- 🐛 Report a bug
- ✨ Request a feature
- 📖 Contribute to the project
- 🔒 Security policy
- 🤝 Code of Conduct
- 💬 Discussions & Q&A
Building with haze-ui? We would love to feature you. Open an issue
titled used by: <your project> with the project name, a link and (optionally)
a logo, and it will be added to this README.