Installation
The packages, the peers you may not need, one Tailwind build 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, lucide-react for icons, and Tailwind CSS v4 — the library ships Tailwind
source, and your build compiles it.
npm install react react-dom lucide-react tailwindcssEverything 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 | @kanzo-tech/mosaic (with its peers @uwdata/mosaic-core and @uwdata/mosaic-sql), @uwdata/vgplot, @uwdata/mosaic-plot, valibot |
@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, 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/graph @kanzo-tech/mosaic @kanzo-tech/ui lucide-react reactEvery peer is required: @kanzo-tech/mosaic is the engine the graph reads through and the
crossfilter it shares with the charts. The graph imports nothing of graphar's or fossil's — the host
attaches a GraphAr graph with @kanzo-tech/graphar-duckdb's attachGraph, its own dependency, and
names the catalog to the root.
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:
Assist asks one and Chat draws its answers, 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/ai @kanzo-tech/llm ai @ai-sdk/reactThe model is reached through @kanzo-tech/llm, which has no React: localModel, on
@kanzo-tech/llm/local, runs a model in the page, and every model call goes through its stream or
generate, asked once. The AI SDK's own names — the agent, tool, the message types — are imported
from ai. ai and @ai-sdk/react are required peers, installed once, because the host's agent and
these components must share one copy of the SDK's types. /local's WebLLM peers are optional, and
only that subpath reaches them. Everything ai draws with is @kanzo-tech/ui and Ark, both required peers, and its
appearance is one more line in the stylesheet below — its own @sources, streamdown's included, and
nothing else.
Authentication is a package that draws nothing
@kanzo-tech/auth is the third sibling, and it is the one that needs none of the
above: it does not import @kanzo-tech/ui, it ships no component and it has no stylesheet. The
first admission rule excludes auth by name, for the reason the log-out affordance is missing from
the sidebar — a library whose vocabulary every product shares should not
know what a session is.
pnpm add @kanzo-tech/authThe session is read on your server from the token the platform's edge forwards and handed to
AuthProvider as a prop (the session page), so the
browser half needs nothing else. The root barrel carries no engine, which is what makes it the door
every consumer's browser half opens. The other two each name theirs, and you install only the one you
open.
| subpath | what it is | install alongside |
|---|---|---|
@kanzo-tech/auth/server | sessionReader: the forwarded token verified and read into a Session, framework-agnostic | jose |
@kanzo-tech/auth/next | kanzoAuth: the server component's session, the proxy and the API forwarder | jose, next 16+ |
The two server doors are Node's, and nothing on them reaches React —
packages/auth/src/server.test.ts imports them without React to say so, rather than trusting this
sentence.
Import the entries into your Tailwind stylesheet
@import "tailwindcss";
@import "@kanzo-tech/ui/tailwind.css";
@import "@kanzo-tech/ai/tailwind.css"; /* only with @kanzo-tech/ai */
@import "@kanzo-tech/graph/tailwind.css"; /* only with @kanzo-tech/graph */The entries are Tailwind source, not compiled CSS. @kanzo-tech/ui's carries the tokens, the
generated themes, the dark variant, the typography plugin and the @source that finds its own
components; @kanzo-tech/ai's adds its components and streamdown's, and @kanzo-tech/graph's the
parts around its canvas. Your build compiles the page
once — one preflight, one utilities layer, one variant order — and your own utilities resolve
against the same tokens. Do not import @kanzo-tech/theme/tokens.css beside them: the entry
already does.
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>;
}Running the services
@kanzo-tech/auth talks to a server, and that server ships from this repository under the same tag
as the packages: the identity service, Keycloak with the platform realm as
code. A product adopting it includes the service's compose.yaml — and, in development,
its compose.dev.yaml — by URL at the release its packages came from, the same files in development
and in production, and declares itself — a client and its roles, and its organizations — from its
own repository.
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 theme 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.