monorepo
Set up duck-ui in a monorepo with shared UI packages.
Overview
duck-ui supports monorepo setups where UI components live in a shared package consumed by multiple apps. The CLI handles workspace detection, config placement, and component installation paths automatically.
Typical workspace structure
my-monorepo/
├── apps/
│ ├── web/ # Next.js app
│ └── docs/ # Docs site
├── packages/
│ └── ui/ # Shared UI package
│ ├── src/
│ │ ├── components/
│ │ │ └── ui/
│ │ ├── hooks/
│ │ ├── libs/
│ │ └── styles.css
│ ├── duck-ui.config.json
│ ├── package.json
│ └── tsconfig.json
├── package.json
├── pnpm-workspace.yaml
└── turbo.json
Initialize with the CLI
Run the init command from the monorepo root, specifying the workspace where components should be installed:
npx @gentleduck/cli init --monorepo --workspace packages/ui
npx @gentleduck/cli init --monorepo --workspace packages/ui
Or use the interactive prompts (without --yes):
npx @gentleduck/cli init
npx @gentleduck/cli init
The CLI will detect your workspaces and prompt you to select the target package.
Config format
The CLI generates a duck-ui.config.json in the workspace directory:
{
"schema": "https://ui.gentleduck.org/schema.json",
"rsc": false,
"monorepo": true,
"workspace": {
"root": ".",
"project": "."
},
"tailwind": {
"baseColor": "zinc",
"css": "./src/styles.css",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"ui": "~/ui",
"libs": "~/libs",
"hooks": "~/hooks",
"pages": "~/pages",
"layouts": "~/layouts"
}
}{
"schema": "https://ui.gentleduck.org/schema.json",
"rsc": false,
"monorepo": true,
"workspace": {
"root": ".",
"project": "."
},
"tailwind": {
"baseColor": "zinc",
"css": "./src/styles.css",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"ui": "~/ui",
"libs": "~/libs",
"hooks": "~/hooks",
"pages": "~/pages",
"layouts": "~/layouts"
}
}Adding components
Add components to the shared UI package by specifying the workspace:
npx @gentleduck/cli add button --workspace packages/ui npx @gentleduck/cli add dialog card --workspace packages/ui
npx @gentleduck/cli add button --workspace packages/ui npx @gentleduck/cli add dialog card --workspace packages/ui
Components are installed relative to the workspace's configured aliases.
CSS setup
The styles file lives in the shared UI package (e.g. packages/ui/src/styles.css). The CLI generates the full CSS including theme variables, @theme inline, base layer styles, utilities, and reduced-motion preferences.
Apps import the styles from the shared package:
import '@my-org/ui/styles.css'import '@my-org/ui/styles.css'Or in Next.js:
import '@my-org/ui/styles.css'import '@my-org/ui/styles.css'Make sure the shared package exports the CSS file in its package.json:
{
"exports": {
"./styles.css": "./src/styles.css",
"./*": "./src/components/ui/*"
}
}{
"exports": {
"./styles.css": "./src/styles.css",
"./*": "./src/components/ui/*"
}
}Shared UI package pattern
The shared package typically re-exports all components:
export * from './components/ui/button'
export * from './components/ui/dialog'
export * from './components/ui/card'export * from './components/ui/button'
export * from './components/ui/dialog'
export * from './components/ui/card'Apps consume components from the package:
import { Button } from '@my-org/ui'import { Button } from '@my-org/ui'Working example
The acme template is a complete working monorepo example with a shared UI package, multiple apps, and Turborepo configuration. Scaffold it directly with:
npx @gentleduck/cli init --template acme --cwd my-project
npx @gentleduck/cli init --template acme --cwd my-project
Or clone the repo and reference it manually as a starting point for your own monorepo setup.