Registry
ZUI components ship two ways. Both give you the same code; they differ in who owns it afterwards.
npm or registry
npm (@zuilib/components) | Registry (source) | |
|---|---|---|
| Install | pnpm add @zuilib/components @zuilib/tokens | npx @zuilib/cli add button |
| Import | import Button from '@zuilib/components/button' | import Button from '@/components/zui/button' |
| Code lives in | node_modules | your repo (components/zui/) |
| Upgrades | bump the version | zui update and review the diff |
| Customisation | override tokens on :root or a subtree | override tokens, or edit the file |
| Build requirement | none (zui.css is prebuilt) | Tailwind v4 in your app |
Pick npm when you want a dependency: one line to upgrade, no code to maintain, styling through the token contract only. Pick the registry when the component must become yours: you intend to change its markup or behaviour, or your organisation forbids runtime UI dependencies. Mixing is fine. A project can install @zuilib/tokens from npm and pull only the components it wants to fork from the registry.
Only @zuilib/components is in the registry (with the token css it depends on). @zuilib/form, table, toast and text-editor are npm packages.
Set up
Your project needs Tailwind v4. Run once, in the project root:
npx @zuilib/cli init
That writes zui.json, installs the token css and the stylesheet entry under styles/zui/ (src/styles/zui/ when the project has a src/ folder) and adds one line to your global stylesheet, after @import "tailwindcss":
@import "tailwindcss";
@import "./styles/zui/zui.css";
Dark mode is the dark class on <html>, exactly as with the npm package. init guesses the directories; --components, --styles, --css override them, and everything is editable in zui.json afterwards:
{
"registry": "https://zuilib.com/r",
"version": "latest",
"components": "src/components/zui",
"styles": "src/styles/zui",
"css": "src/index.css"
}
Set version to a release ("0.1.0") to pin: every add and update then reads /r/v/0.1.0/ and nothing moves until you change it.
Add a component
npx @zuilib/cli add button
That writes components/zui/button.tsx, then follows the item's registryDependencies: the helpers it imports land in components/zui/lib/ (the same tree as the registry, so the relative imports in the source work unchanged), and npm dependencies the component needs (@headlessui/react, clsx, tailwind-merge, @floating-ui/react) are installed with your package manager at the versions ZUI is built against. Several items in one go:
npx @zuilib/cli add input select dialog
Every write is recorded in zui.lock.json with the file's sha256, which is what makes the next two commands possible.
Keeping up to date
npx @zuilib/cli list # ✓ installed, ↑ newer in the registry
npx @zuilib/cli diff # unified diff, installed files vs registry
npx @zuilib/cli update # rewrite files the registry changed
update rewrites a file only when your copy still matches what the CLI last wrote. A file you edited is reported and left alone; zui diff <item> shows both sides, zui update --force takes the registry's version. Keeping your changes in tokens (:root overrides) rather than in the component files keeps those conflicts rare.
What is in the registry
| Item | Type | Contents |
|---|---|---|
<component> (button, listbox, table, …) | component | One component file, target components/zui/<name>.tsx |
lib-<helper> (lib-cn, lib-control-frame, …) | lib | One shared helper, target components/zui/lib/<helper>.ts |
zui-tokens | theme | The token contract: light and dark custom properties, keyframes, animations, the Tailwind @theme mapping and base styles |
zui-styles | theme | The stylesheet entry (styles/zui/zui.css) plus the component css Tailwind cannot express |
The registry is static json, no server behind it:
| Url | Contents |
|---|---|
/r/latest/index.json | Every item: type, version, description, npm dependencies, registry dependencies, file targets |
/r/latest/button.json | One item with its file contents and their sha256; an item can be inspected without installing it |
/r/v/<version>/… | The same, frozen per @zuilib/components release |
/r/schema.json | JSON schema of the index and item documents |
Tokens alone, for a project that only wants the design contract:
npx @zuilib/cli add zui-tokens
For coding agents: llms.txt
The site publishes two files in the llms.txt format so an agent can learn the library without crawling the docs:
/llms.txt: the site map. One line per doc page, one line per component with its import line and purpose, one line per helper and theme item, each linking to its registry json./llms-full.txt: the full component API reference (setup, import rules, every component's props, slots and conventions) and the complete token contract, in one file.
Point an agent at https://zuilib.com/llms.txt in its instructions (CLAUDE.md, AGENTS.md, .cursorrules) and it can pick the right component, write the correct import and know which tokens to override. Give it llms-full.txt when it needs prop-level detail offline. An agent with shell access installs components the same way you do: npx @zuilib/cli add <item>.
Building the registry
Everything under /r/, llms.txt and llms-full.txt is generated from packages/components/src, packages/tokens/src and the components README by packages/components/scripts/build-registry.mjs. It resolves each component's relative imports into registryDependencies and its package imports into dependencies, hashes every file, writes /r/latest/ and /r/v/<version>/, and fails when an import cannot be matched to an item. Run it from the repo root:
pnpm registry:build
pnpm build and pnpm docs:build run it too, so the deployed site never carries a stale registry. Deploying the registry is deploying the docs site.