Kanzo UI
Getting started

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/ui

Install the peers

React 19 or later, plus lucide-react for icons.

npm install react react-dom lucide-react

Everything 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:

SubpathWhat it isOptional peers
@kanzo-tech/ui/tableThe connected data table@tanstack/react-table
@kanzo-tech/ui/analyticsThe charts, over Mosaic + DuckDB@uwdata/vgplot, @uwdata/mosaic-core, @uwdata/mosaic-sql, @duckdb/duckdb-wasm
@kanzo-tech/ui/editorThe 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/graph

Its 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/ai

It 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

app/layout.tsx
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

app/layout.tsx
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.

app/layout.tsx
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.

On this page