Installation
Configure shadcn in your Zeno monorepo and add components with the CLI
Zeno's UI primitives are plain shadcn Base UI
components; you add them with the CLI and own the code. Zeno adds a
registry for its own items, such as the theme, the
auth flows, and the form field kit, which you also install with shadcn add. Every file lands in
your repo, so you can customize it.
Project structure
A Zeno monorepo has one shared ui package that owns the primitives and the default theme,
and one or more apps that consume that package and override the theme as needed. The layout
follows the shadcn monorepo convention, with
the theme in the ui package:
apps
└── {yourApp} # a Zeno app; consumes @{yourWorkspace}/ui
├── src
│ ├── app
│ │ └── globals.css # imports the ui theme; per-app token overrides go here
│ ├── components # app-only components (@/components)
│ ├── hooks # app-only hooks (@/hooks)
│ └── lib # app-only helpers (@/lib)
├── components.json
├── package.json
└── tsconfig.json # backs the @/* alias (paths)
packages
└── ui # @{yourWorkspace}/ui; the shared UI package
├── src
│ ├── components
│ │ └── button.tsx # shadcn primitives you add land here
│ ├── hooks
│ ├── lib
│ │ └── utils.ts # cn() and shared helpers
│ └── styles
│ └── globals.css # holds the default Zeno theme
├── components.json
├── package.json # `exports` back the @{yourWorkspace}/ui/* aliases
└── tsconfig.json
package.json
turbo.jsonConfigure shadcn
An upcoming zeno-cli will generate this whole structure for you. Until then, configure it by hand.
shadcn-cli doesn't generate this monorepo layout well.
Give each app and the shared ui package a components.json. Copy the files below into your
project and edit them as needed, replacing {yourApp} with your app's directory and
@{yourWorkspace} with your monorepo's package scope:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "base-nova",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"ui": "@{yourWorkspace}/ui/components",
"lib": "@/lib",
"hooks": "@/hooks",
"utils": "@{yourWorkspace}/ui/lib/utils"
}
}{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "base-nova",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"iconLibrary": "lucide",
"aliases": {
"components": "@{yourWorkspace}/ui/components",
"ui": "@{yourWorkspace}/ui/components",
"lib": "@{yourWorkspace}/ui/lib",
"hooks": "@{yourWorkspace}/ui/hooks",
"utils": "@{yourWorkspace}/ui/lib/utils"
}
}Keep style, baseColor, and iconLibrary identical across both files. Running add from the
app path installs primitives into packages/ui and wires the imports in the app. The
shadcn monorepo guide explains why.
Back the aliases
Declare the @{yourWorkspace}/ui/* aliases in the shared package's exports:
{
"name": "@{yourWorkspace}/ui",
"exports": {
"./components/*": "./src/components/*.tsx",
"./hooks/*": "./src/hooks/*.ts",
"./lib/*": "./src/lib/*.ts",
"./styles/*.css": "./src/styles/*.css"
}
}Then give the app its @/* alias. The shared package needs no paths:
{
"extends": "@zeno-lib/typescript/nextjs.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}{
"extends": "@zeno-lib/typescript/react.json"
}Add the theme
Add Zeno's default theme to the shared ui package:
pnpm dlx shadcn@latest add zeno-lib/zeno/theme -c packages/uiThen make sure the shared stylesheet imports Tailwind and the animations, and declares the dark variant:
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));For the full token reference, dark mode, and how to override the palette, see Theming.
Add primitives
Add the shadcn primitives you need with the CLI. See UI primitives.