Installation
The packages, the peers you may not need, one stylesheet and one provider.
Install the packages
@kanzo-tech/ui depends on @kanzo-tech/theme, so installing the first brings the second.
npm install @kanzo-tech/uiInstall the peers
React 19 or later, plus lucide-react for icons.
npm install react react-dom lucide-reactEverything else is optional, and only if you import the matching subpath. Each subpath statically re-exports its engine, so installing its peers is what buys you that subpath and nothing else:
| Subpath | What it is | Optional peers |
|---|---|---|
@kanzo-tech/ui/table | The connected data table | @tanstack/react-table |
@kanzo-tech/ui/analytics | The charts, over Mosaic + DuckDB | @uwdata/vgplot, @uwdata/mosaic-core, @uwdata/mosaic-sql, @duckdb/duckdb-wasm |
@kanzo-tech/ui/editor | The CodeMirror editors | @codemirror/state, @codemirror/view, @codemirror/language, @codemirror/commands, @codemirror/search, @codemirror/autocomplete, @lezer/highlight |
The base entry never imports any of them. That is deliberate — a static import of an optional
peer from the root barrel makes import { Button } throw for everyone who did not install it,
and it is why each of these has a presentational half in the root barrel and a connected half on
the subpath. See the engine rule.
The graph is a package, not a subpath
@kanzo-tech/graph is installed separately, and the reason it is not a fourth row
above is the reason it is not in @kanzo-tech/ui at all: its renderer peer is required, not
optional. There is nothing left of the package without one, and a required WebGL dependency would
change what the base library is — the first admission rule
excludes graphs by name.
pnpm add @kanzo-tech/graph @cosmos.gl/graphIts own database half sits behind @kanzo-tech/graph/duckdb, on the same rule and for the same
reason: a host drawing arrays it already holds takes memorySource and pays for no database, and
the subpath is what makes that promise true rather than merely stated.
The AI surfaces are a package too
@kanzo-tech/ai is the other sibling, and it is separate for a different reason — not a
required renderer, but the barrel itself. The line is does the component know a model exists: a
Message has a role and one of the roles is assistant, which is domain knowledge the generic
vocabulary does not carry, and a consumer who wants a Button must not pay for a transcript.
pnpm add @kanzo-tech/aiIt has no optional peer and no subpath. Everything it draws with is @kanzo-tech/ui and Ark, both
required peers, and its appearance comes from the same styles.css you import below — there is no
second sheet.
Import the stylesheet once, at the root
import "@kanzo-tech/ui/styles.css";This one sheet carries the tokens, the generated themes and every component's compiled CSS. There is no runtime style injection, so no flash of unstyled content.
Wrap the app in the provider
import { KanzoThemeProvider } from "@kanzo-tech/ui";
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<KanzoThemeProvider>{children}</KanzoThemeProvider>
</body>
</html>
);
}Use a component
import { Button } from "@kanzo-tech/ui";
export default function Page() {
return <Button>Get started</Button>;
}Server-side rendering
Render themeScript() in <head> so the theme attributes land before first paint. That one is
not optional for an SSR host: every axis lives in browser storage, so without it the server paints
the defaults and the client re-skins on hydration — a flash, plus a hydration mismatch in any
control whose markup depends on the resolved appearance.
cookieStorageAdapter() is the optional half. It lets the server read the same source the client
writes, so the rendered <html> already carries the attributes rather than acquiring them a moment
later. Colour is not a reason to add it: every palette a tenant publishes ships in the page at once
and the choice is an attribute, so the script applies it like any other axis.
import { KanzoThemeProvider, themeScript, cookieStorageAdapter } from "@kanzo-tech/ui";
<head>
<script dangerouslySetInnerHTML={{ __html: themeScript() }} />
</head>
<KanzoThemeProvider storage={cookieStorageAdapter()}>{children}</KanzoThemeProvider>See Theming for dark mode and the full axis list.