zard/ui
An Angular component library. Components are installed as source code into the user's project by the CLI — there is no runtime package to import from, and no component to npm install.
IMPORTANT: Run every CLI command with the project's own package runner: npx zard-cli, pnpm dlx zard-cli, yarn zard-cli, or bunx zard-cli — pick the one matching packageManager in components.json. The examples below use npx zard-cli; substitute the right runner.
Current Project Context
Read components.json at the project root before doing anything else. It is written by zard-cli init and is the whole configuration — there is no zard-cli info command to call.
{
"$schema": "https://zardui.com/schema.json",
"style": "css",
"icons": "lucide",
"rtl": false,
"projectType": "angular",
"appConfigFile": "src/app/app.config.ts",
"packageManager": "npm",
"tailwind": { "css": "src/styles.css", "baseColor": "neutral" },
"baseUrl": "src/app",
"aliases": {
"components": "@/shared/components",
"utils": "@/shared/utils",
"core": "@/shared/core",
"services": "@/shared/services"
}
}
No components.json means the project has not been initialised — run init rather than writing component files by hand.
For anything the file does not answer — what exists, what a component's API is, what it depends on — read the registry or use the MCP server. Never reconstruct a component API from memory.
Key Fields
aliases.components → the import prefix for every component. Use the actual value; never hardcode @/shared/components. The prefix can be anything (@app/..., ~/...) — it is mapped in tsconfig.json, or tsconfig.base.json in an Nx workspace.
baseUrl → the source root the aliases resolve against. Components are written under it.
projectType → angular, angular-library, nx, nx-library, or analog. Decides which tsconfig holds the aliases, where Tailwind is configured, and whether an app.config.ts exists at all. See cli.md.
appConfigFile → where provideZard() is registered. Empty in a library — there the consuming app registers it.
tailwind.css → the global CSS file holding the theme tokens. Always edit this file; never create a second one.
tailwind.baseColor → the theme preset: neutral, stone, zinc, gray, or slate.
icons → the icon family the components are written with (lucide today). Decides the @ng-icons/* package and the symbol names. See rules/icons.md.
rtl → declares layout direction intent. It does not change what gets installed.
packageManager → use it for every dependency install (pnpm add date-fns, not npm install date-fns) and to pick the CLI runner.
registryUrl → optional. Present when the project installs from a registry other than https://zardui.com/r. See registry.md.
Principles
- Install before importing. A component only exists once
zard-cli add has written it. Check the components directory first; do not import what is not there.
- Compose what exists. A settings page is Card + Field + Input + Button. A dashboard is Layout + Card + Chart + Table. Reach for custom markup only when nothing covers it.
- Variants before classes.
zType="outline", zSize="sm" — not a class that re-styles the component into the same thing.
- Semantic tokens only.
bg-primary, text-muted-foreground. Never bg-blue-500, never a dark: colour override.
- The library's own conventions apply to the code you write. Standalone,
OnPush, input(), z-prefixed inputs. See rules/angular.md.
Critical Rules
Always enforced. Each links to a file with Incorrect/Correct pairs.
- Standalone with
imports, ChangeDetectionStrategy.OnPush, ViewEncapsulation.None. No NgModules, no Default change detection.
- Signal inputs:
input(), model(), output(). No @Input() / @Output() decorators in new code.
- Selectors are
z-<name> and/or [z-<name>]. Some components are element-only, some are attributes on a native tag (input[z-input], button[z-button]). Use the one the component declares.
- Composite components import their
Zard<Name>Imports array, not the individual classes one by one.
- Never edit generated output.
apps/web/src/generated/** and public/r/*.json are build artefacts.
- Semantic tokens, never raw colours.
bg-primary not bg-blue-600.
class is for layout, not for restyling. It is merged last and wins — which is exactly why it should not be used to override the component's own colours.
mergeClasses(), not string concatenation. It is twMerge(clsx(...)); plain interpolation loses the conflict resolution.
- No
space-x-* / space-y-*. Use flex with gap-*.
size-* when width and height match. size-4, not w-4 h-4.
- No
dark: colour overrides. The tokens already switch.
- Tailwind v4 only. There is no
tailwind.config.js; the theme lives in CSS.
scroll-fade needs an overflow container; shimmer is text-only. Both are pure-CSS utilities from the core item.
- Use the full composition.
z-card wants z-card-header / z-card-title / z-card-content / z-card-footer, not everything dumped into content.
- Items belong to their group.
z-select-item inside z-select-group.
- Dialogs are opened through
ZardDialogService, not by putting a z-dialog in the template with an open flag.
- Toasts go through
ZardSonnerService — show, success, error, promise.
- Use the component instead of styled markup.
z-separator not <hr>, z-skeleton not an animate-pulse div, z-badge not a styled span, z-empty not a hand-built empty state.
- All three Angular form APIs are supported: Signal Forms, Reactive Forms, Template-driven. Follow whichever the project already uses.
- Form layout is
z-field-group + z-field, never a div with space-y-*.
- Validation state is
data-invalid on the field and aria-invalid on the control.
- Errors render in
z-field-error, not a loose paragraph.
- Rendered markdown gets a
typeset container, never a class per tag. zard-cli add typeset installs the stylesheet.
- Six variables govern it, three of which are the rhythm:
--typeset-size, --typeset-leading, --typeset-flow.
not-typeset on any component embedded in prose. It brings its own sizing.
typeset-scroll around a wide table, instead of a hand-rolled overflow wrapper.
- Utilities beat it with no
!important — every element selector sits inside :where().
- Icons come from
@ng-icons/<family> and are registered with provideIcons in viewProviders. An unregistered name renders nothing, silently.
- The family comes from
icons in components.json. Do not assume lucide.
- No sizing classes on icons inside components. The component sizes them.
Key Patterns
// Host classes: computed + mergeClasses. Never assembled in the template.
protected readonly classes = computed(() => mergeClasses(cardVariants(), this.class()));
// Variants, not classes.
<button z-button zType="outline" zSize="sm">Save</button> // correct
<button z-button class="border bg-transparent px-2.5">Save</button> // wrong
// Spacing: gap-*, not space-y-*.
<div class="flex flex-col gap-4"> // correct
<div class="space-y-4"> // wrong
// Field: data-invalid on the field, aria-invalid on the control.
<div z-field [attr.data-invalid]="invalid || null">
<label z-field-label for="email">Email</label>
<input z-input id="email" [attr.aria-invalid]="invalid || null" />
<z-field-error>Enter a valid email.</z-field-error>
</div>
// Loading button: the zLoading input, not a hand-rolled spinner.
<button z-button [zLoading]="saving()" [zDisabled]="saving()">Save</button>
Component Selection
| Need |
Use |
| Action |
z-button (zType: default, destructive, outline, secondary, ghost, link) |
| Grouped actions |
z-button-group, z-toggle, z-toggle-group |
| Text input |
input[z-input], textarea[z-textarea], z-input-group, z-input-otp |
| Choice |
z-select, z-combobox, z-radio-group, z-checkbox, z-switch, z-slider |
| Dates |
z-calendar, z-date-picker |
| Form structure |
z-field-group, z-field, z-field-label, z-field-description, z-field-error |
| Data display |
z-table, z-card, z-item, z-badge, z-avatar, z-chart |
| Navigation |
z-navigation-menu, z-breadcrumb, z-tab-group, z-pagination, z-tree |
| Layout |
z-layout, z-separator, z-resizable, z-accordion, z-carousel |
| Overlays |
ZardDialogService (modal), z-drawer (bottom/side sheet), z-sheet (side panel), z-alert-dialog (confirmation), z-popover, z-tooltip, z-dropdown |
| Command palette |
z-command |
| Feedback |
ZardSonnerService (toast), z-alert, z-progress, z-skeleton, z-spinner |
| Empty states |
z-empty |
| Chat / messages |
z-bubble, z-bubble-group, z-bubble-content, z-bubble-reactions |
| Keyboard hints |
z-kbd |
| Rendered prose |
typeset + a preset class (a stylesheet, not a component) — see rules/typeset.md |
Names are the registry names — the same string zard-cli add takes.
Workflow
- Read
components.json. No file → run init. Note aliases, baseUrl, icons, packageManager, projectType.
- Check what is installed. List the components directory resolved from
aliases.components. Do not re-add what is there, and do not import what is not.
- Find what exists. The registry index at
<registryUrl>/registry.json lists every item; the MCP server exposes the same thing as list-components and search-components.
- Read the real API before writing code.
get-component-docs (MCP) or https://zardui.com/docs/components/<name>.md. Every component page is published as Markdown. Guessing at inputs is the single most common failure mode.
- Install.
npx zard-cli add <name> — dependencies of the component, both npm packages and other registry items, are resolved and installed with it.
- Review what was written. Read the added files. Check the imports resolve under the project's real alias, and that the icon family matches
icons.
- Never hand-fetch component source from GitHub. Use the CLI or the registry; the registry is what the project actually installs from.
Quick Reference
# Initialise a project (full-screen wizard).
npx zard-cli init
npx zard-cli init --type nx --project web # answer the wizard up front
npx zard-cli init --yes # accept the defaults; required without a TTY
# Add components.
npx zard-cli add button card dialog
npx zard-cli add # pick from the list
npx zard-cli add --all
npx zard-cli add button --overwrite # replace local changes — ask first
npx zard-cli add button --path src/app/ui # a directory other than the configured one
# Diagnose.
npx zard-cli add button --debug
npx zard-cli --version
There is no search, view, diff, info, or build command — those are shadcn's. See cli.md for the full flag reference.
Detailed References
- cli.md —
init and add, every flag, the five project types, headless behaviour
- registry.md — the index, item and icon-catalog formats, JSON Schemas, pointing at your own registry
- mcp.md — the nine MCP tools, how to connect,
ZARD_REGISTRY_URL / ZARD_DOCS_URL
- customization.md — theme tokens, CVA variants,
mergeClasses, extending a component
- rules/angular.md — standalone,
input(), OnPush, ViewEncapsulation.None, selectors
- rules/styling.md — Tailwind v4, semantic tokens,
mergeClasses, variants before raw classes, the scroll-fade / shimmer utilities
- rules/composition.md — composing with what exists before inventing markup
- rules/forms.md — Signal Forms, Reactive Forms, Template-driven
- rules/icons.md — ng-icons,
provideIcons, the catalog, the configurable family
- rules/typeset.md — styling rendered markdown with one container class instead of one per tag
1---2name: zard3description: Manages zard/ui components and projects — adding, composing, styling, and debugging Angular UI built on TailwindCSS v4. Provides project context, component source, and the real API instead of a remembered one. Applies when working with zard/ui, zard-cli, the zard registry, or any project with a components.json that declares a zard projectType. Also triggers for "zard-cli init", "add a zard component", or "set up zard/ui in an Nx workspace".4---56# zard/ui78An Angular component library. Components are installed as source code into the user's project by the CLI — there is no runtime package to import from, and no component to `npm install`.910> **IMPORTANT:** Run every CLI command with the project's own package runner: `npx zard-cli`, `pnpm dlx zard-cli`, `yarn zard-cli`, or `bunx zard-cli` — pick the one matching `packageManager` in `components.json`. The examples below use `npx zard-cli`; substitute the right runner.1112## Current Project Context1314**Read `components.json` at the project root before doing anything else.** It is written by `zard-cli init` and is the whole configuration — there is no `zard-cli info` command to call.1516```json17{18 "$schema": "https://zardui.com/schema.json",19 "style": "css",20 "icons": "lucide",21 "rtl": false,22 "projectType": "angular",23 "appConfigFile": "src/app/app.config.ts",24 "packageManager": "npm",25 "tailwind": { "css": "src/styles.css", "baseColor": "neutral" },26 "baseUrl": "src/app",27 "aliases": {28 "components": "@/shared/components",29 "utils": "@/shared/utils",30 "core": "@/shared/core",31 "services": "@/shared/services"32 }33}34```3536No `components.json` means the project has not been initialised — run `init` rather than writing component files by hand.3738For anything the file does not answer — what exists, what a component's API is, what it depends on — read the [registry](./registry.md) or use the [MCP server](./mcp.md). Never reconstruct a component API from memory.3940## Key Fields4142- **`aliases.components`** → the import prefix for every component. Use the actual value; never hardcode `@/shared/components`. The prefix can be anything (`@app/...`, `~/...`) — it is mapped in `tsconfig.json`, or `tsconfig.base.json` in an Nx workspace.43- **`baseUrl`** → the source root the aliases resolve against. Components are written under it.44- **`projectType`** → `angular`, `angular-library`, `nx`, `nx-library`, or `analog`. Decides which tsconfig holds the aliases, where Tailwind is configured, and whether an `app.config.ts` exists at all. See [cli.md](./cli.md).45- **`appConfigFile`** → where `provideZard()` is registered. **Empty in a library** — there the consuming app registers it.46- **`tailwind.css`** → the global CSS file holding the theme tokens. Always edit this file; never create a second one.47- **`tailwind.baseColor`** → the theme preset: `neutral`, `stone`, `zinc`, `gray`, or `slate`.48- **`icons`** → the icon family the components are written with (`lucide` today). Decides the `@ng-icons/*` package and the symbol names. See [rules/icons.md](./rules/icons.md).49- **`rtl`** → declares layout direction intent. It does not change what gets installed.50- **`packageManager`** → use it for every dependency install (`pnpm add date-fns`, not `npm install date-fns`) and to pick the CLI runner.51- **`registryUrl`** → optional. Present when the project installs from a registry other than `https://zardui.com/r`. See [registry.md](./registry.md).5253## Principles54551. **Install before importing.** A component only exists once `zard-cli add` has written it. Check the components directory first; do not import what is not there.562. **Compose what exists.** A settings page is Card + Field + Input + Button. A dashboard is Layout + Card + Chart + Table. Reach for custom markup only when nothing covers it.573. **Variants before classes.** `zType="outline"`, `zSize="sm"` — not a `class` that re-styles the component into the same thing.584. **Semantic tokens only.** `bg-primary`, `text-muted-foreground`. Never `bg-blue-500`, never a `dark:` colour override.595. **The library's own conventions apply to the code you write.** Standalone, `OnPush`, `input()`, `z`-prefixed inputs. See [rules/angular.md](./rules/angular.md).6061## Critical Rules6263Always enforced. Each links to a file with Incorrect/Correct pairs.6465### Angular API → [rules/angular.md](./rules/angular.md)6667- **Standalone with `imports`, `ChangeDetectionStrategy.OnPush`, `ViewEncapsulation.None`.** No NgModules, no `Default` change detection.68- **Signal inputs: `input()`, `model()`, `output()`.** No `@Input()` / `@Output()` decorators in new code.69- **Selectors are `z-<name>` and/or `[z-<name>]`.** Some components are element-only, some are attributes on a native tag (`input[z-input]`, `button[z-button]`). Use the one the component declares.70- **Composite components import their `Zard<Name>Imports` array**, not the individual classes one by one.71- **Never edit generated output.** `apps/web/src/generated/**` and `public/r/*.json` are build artefacts.7273### Styling → [rules/styling.md](./rules/styling.md)7475- **Semantic tokens, never raw colours.** `bg-primary` not `bg-blue-600`.76- **`class` is for layout, not for restyling.** It is merged last and wins — which is exactly why it should not be used to override the component's own colours.77- **`mergeClasses()`, not string concatenation.** It is `twMerge(clsx(...))`; plain interpolation loses the conflict resolution.78- **No `space-x-*` / `space-y-*`.** Use `flex` with `gap-*`.79- **`size-*` when width and height match.** `size-4`, not `w-4 h-4`.80- **No `dark:` colour overrides.** The tokens already switch.81- **Tailwind v4 only.** There is no `tailwind.config.js`; the theme lives in CSS.82- **`scroll-fade` needs an overflow container; `shimmer` is text-only.** Both are pure-CSS utilities from the `core` item.8384### Composition → [rules/composition.md](./rules/composition.md)8586- **Use the full composition.** `z-card` wants `z-card-header` / `z-card-title` / `z-card-content` / `z-card-footer`, not everything dumped into content.87- **Items belong to their group.** `z-select-item` inside `z-select-group`.88- **Dialogs are opened through `ZardDialogService`**, not by putting a `z-dialog` in the template with an `open` flag.89- **Toasts go through `ZardSonnerService`** — `show`, `success`, `error`, `promise`.90- **Use the component instead of styled markup.** `z-separator` not `<hr>`, `z-skeleton` not an `animate-pulse` div, `z-badge` not a styled span, `z-empty` not a hand-built empty state.9192### Forms → [rules/forms.md](./rules/forms.md)9394- **All three Angular form APIs are supported**: Signal Forms, Reactive Forms, Template-driven. Follow whichever the project already uses.95- **Form layout is `z-field-group` + `z-field`**, never a `div` with `space-y-*`.96- **Validation state is `data-invalid` on the field and `aria-invalid` on the control.**97- **Errors render in `z-field-error`**, not a loose paragraph.9899### Typeset → [rules/typeset.md](./rules/typeset.md)100101- **Rendered markdown gets a `typeset` container, never a class per tag.** `zard-cli add typeset` installs the stylesheet.102- **Six variables govern it**, three of which are the rhythm: `--typeset-size`, `--typeset-leading`, `--typeset-flow`.103- **`not-typeset` on any component embedded in prose.** It brings its own sizing.104- **`typeset-scroll` around a wide table**, instead of a hand-rolled overflow wrapper.105- **Utilities beat it with no `!important`** — every element selector sits inside `:where()`.106107### Icons → [rules/icons.md](./rules/icons.md)108109- **Icons come from `@ng-icons/<family>` and are registered with `provideIcons` in `viewProviders`.** An unregistered name renders nothing, silently.110- **The family comes from `icons` in `components.json`.** Do not assume `lucide`.111- **No sizing classes on icons inside components.** The component sizes them.112113## Key Patterns114115```angular-ts116// Host classes: computed + mergeClasses. Never assembled in the template.117protected readonly classes = computed(() => mergeClasses(cardVariants(), this.class()));118119// Variants, not classes.120<button z-button zType="outline" zSize="sm">Save</button> // correct121<button z-button class="border bg-transparent px-2.5">Save</button> // wrong122123// Spacing: gap-*, not space-y-*.124<div class="flex flex-col gap-4"> // correct125<div class="space-y-4"> // wrong126127// Field: data-invalid on the field, aria-invalid on the control.128<div z-field [attr.data-invalid]="invalid || null">129 <label z-field-label for="email">Email</label>130 <input z-input id="email" [attr.aria-invalid]="invalid || null" />131 <z-field-error>Enter a valid email.</z-field-error>132</div>133134// Loading button: the zLoading input, not a hand-rolled spinner.135<button z-button [zLoading]="saving()" [zDisabled]="saving()">Save</button>136```137138## Component Selection139140| Need | Use |141| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |142| Action | `z-button` (`zType`: default, destructive, outline, secondary, ghost, link) |143| Grouped actions | `z-button-group`, `z-toggle`, `z-toggle-group` |144| Text input | `input[z-input]`, `textarea[z-textarea]`, `z-input-group`, `z-input-otp` |145| Choice | `z-select`, `z-combobox`, `z-radio-group`, `z-checkbox`, `z-switch`, `z-slider` |146| Dates | `z-calendar`, `z-date-picker` |147| Form structure | `z-field-group`, `z-field`, `z-field-label`, `z-field-description`, `z-field-error` |148| Data display | `z-table`, `z-card`, `z-item`, `z-badge`, `z-avatar`, `z-chart` |149| Navigation | `z-navigation-menu`, `z-breadcrumb`, `z-tab-group`, `z-pagination`, `z-tree` |150| Layout | `z-layout`, `z-separator`, `z-resizable`, `z-accordion`, `z-carousel` |151| Overlays | `ZardDialogService` (modal), `z-drawer` (bottom/side sheet), `z-sheet` (side panel), `z-alert-dialog` (confirmation), `z-popover`, `z-tooltip`, `z-dropdown` |152| Command palette | `z-command` |153| Feedback | `ZardSonnerService` (toast), `z-alert`, `z-progress`, `z-skeleton`, `z-spinner` |154| Empty states | `z-empty` |155| Chat / messages | `z-bubble`, `z-bubble-group`, `z-bubble-content`, `z-bubble-reactions` |156| Keyboard hints | `z-kbd` |157| Rendered prose | `typeset` + a preset class (a stylesheet, not a component) — see [rules/typeset.md](./rules/typeset.md) |158159Names are the registry names — the same string `zard-cli add` takes.160161## Workflow1621631. **Read `components.json`.** No file → run `init`. Note `aliases`, `baseUrl`, `icons`, `packageManager`, `projectType`.1642. **Check what is installed.** List the components directory resolved from `aliases.components`. Do not re-add what is there, and do not import what is not.1653. **Find what exists.** The registry index at `<registryUrl>/registry.json` lists every item; the MCP server exposes the same thing as `list-components` and `search-components`.1664. **Read the real API before writing code.** `get-component-docs` (MCP) or `https://zardui.com/docs/components/<name>.md`. Every component page is published as Markdown. Guessing at inputs is the single most common failure mode.1675. **Install.** `npx zard-cli add <name>` — dependencies of the component, both npm packages and other registry items, are resolved and installed with it.1686. **Review what was written.** Read the added files. Check the imports resolve under the project's real alias, and that the icon family matches `icons`.1697. **Never hand-fetch component source from GitHub.** Use the CLI or the registry; the registry is what the project actually installs from.170171## Quick Reference172173```bash174# Initialise a project (full-screen wizard).175npx zard-cli init176npx zard-cli init --type nx --project web # answer the wizard up front177npx zard-cli init --yes # accept the defaults; required without a TTY178179# Add components.180npx zard-cli add button card dialog181npx zard-cli add # pick from the list182npx zard-cli add --all183npx zard-cli add button --overwrite # replace local changes — ask first184npx zard-cli add button --path src/app/ui # a directory other than the configured one185186# Diagnose.187npx zard-cli add button --debug188npx zard-cli --version189```190191There is no `search`, `view`, `diff`, `info`, or `build` command — those are shadcn's. See [cli.md](./cli.md) for the full flag reference.192193## Detailed References194195- [cli.md](./cli.md) — `init` and `add`, every flag, the five project types, headless behaviour196- [registry.md](./registry.md) — the index, item and icon-catalog formats, JSON Schemas, pointing at your own registry197- [mcp.md](./mcp.md) — the nine MCP tools, how to connect, `ZARD_REGISTRY_URL` / `ZARD_DOCS_URL`198- [customization.md](./customization.md) — theme tokens, CVA variants, `mergeClasses`, extending a component199- [rules/angular.md](./rules/angular.md) — standalone, `input()`, OnPush, `ViewEncapsulation.None`, selectors200- [rules/styling.md](./rules/styling.md) — Tailwind v4, semantic tokens, `mergeClasses`, variants before raw classes, the `scroll-fade` / `shimmer` utilities201- [rules/composition.md](./rules/composition.md) — composing with what exists before inventing markup202- [rules/forms.md](./rules/forms.md) — Signal Forms, Reactive Forms, Template-driven203- [rules/icons.md](./rules/icons.md) — ng-icons, `provideIcons`, the catalog, the configurable family204- [rules/typeset.md](./rules/typeset.md) — styling rendered markdown with one container class instead of one per tag