ZenoZENO

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.json

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

apps/{yourApp}/components.json
{
  "$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"
  }
}
packages/ui/components.json
{
  "$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:

packages/ui/package.json
{
  "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:

apps/{yourApp}/tsconfig.json
{
  "extends": "@zeno-lib/typescript/nextjs.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
packages/ui/tsconfig.json
{
  "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/ui

Then make sure the shared stylesheet imports Tailwind and the animations, and declares the dark variant:

packages/ui/src/styles/globals.css
@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.