UI
The project uses shadcn/ui (new-york style) with Tailwind CSS v4 for styling. Components live in packages/ui/ and are shared across all apps in the monorepo.
Component Management
Add components from the shadcn/ui registry:
# Add a single component
bun ui:add button
# Add multiple components
bun ui:add dialog card select
# Interactive mode – browse and select
bun ui:add
# List installed components
bun ui:list
# Update all installed components
bun ui:updateRun bun ui:list to see which components are currently installed.
Component Structure
Components are stored directly in packages/ui/components/ – one file per component:
packages/ui/
├── components/
│ ├── avatar.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── ...
│ └── textarea.tsx
├── lib/
│ └── utils.ts # cn() utility
├── scripts/ # CLI tooling (add, list, update)
├── index.ts # Barrel export
└── package.jsonAll components and utilities are re-exported from the package root (index.ts), so importing is straightforward:
import { Button, Card, CardHeader, CardTitle, Input, cn } from "@repo/ui";Using Components
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@repo/ui";
function FeatureCard({ title, description, children }) {
return (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent>{children}</CardContent>
</Card>
);
}The cn() Utility
Use cn() (from clsx + tailwind-merge) for conditional and merged class names:
import { Button, cn } from "@repo/ui";
<Button
className={cn(
"transition-colors",
isActive && "bg-primary text-primary-foreground",
isDisabled && "opacity-50 cursor-not-allowed",
)}
/>;tailwind-merge resolves conflicts – later classes override earlier ones, so cn("p-4", "p-6") produces "p-6".
Theming
CSS Variables
Theme colors are defined as CSS custom properties in apps/app/styles/globals.css using the OKLCH color space:
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--destructive: oklch(0.577 0.245 27.325);
/* ... */
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
/* ... */
}These variables are mapped to Tailwind utilities in apps/app/tailwind.config.css via @theme inline, so bg-primary, text-muted-foreground, etc. resolve to the CSS variables automatically.
Dark Mode
Dark mode uses a custom Tailwind variant that toggles on the dark class:
/* apps/app/tailwind.config.css */
@custom-variant dark (&:is(.dark *));Customizing Colors
To change the color scheme, update the CSS variables in globals.css. The OKLCH values are independent – changing --primary automatically applies everywhere that uses bg-primary, text-primary, etc.
Tailwind Content Scanning
The Tailwind config uses @source directives to scan both app code and the shared UI package:
/* apps/app/tailwind.config.css */
@import "tailwindcss";
@source "./lib/**/*.{js,ts,jsx,tsx}";
@source "./routes/**/*.{js,ts,jsx,tsx}";
@source "./components/**/*.{js,ts,jsx,tsx}";
@source "../../packages/ui/components/**/*.{ts,tsx}";
@source "../../packages/ui/lib/**/*.{ts,tsx}";
@source "../../packages/ui/hooks/**/*.{ts,tsx}";Troubleshooting
Component not found
If a component import fails, verify it's installed:
bun ui:list
bun ui:add [component-name]Styles not applying
- Check that
globals.cssis imported inapps/app/index.tsx - Verify the Tailwind config includes
@sourcepaths for the UI package - Check that the component file exists in
packages/ui/components/
TypeScript errors
Ensure the workspace reference is set up:
// apps/app/tsconfig.json
{
"references": [{ "path": "../../packages/ui" }]
}