shadcn/ui
The project uses shadcn/ui for UI components. All shadcn components live in packages/frontend-common/components/ui and are shared across both the frontend and admin applications.
Overview
shadcn/ui is a collection of re-usable components built using Radix UI and Tailwind CSS. Unlike traditional component libraries, shadcn components are copied directly into your codebase, giving you full control to customize them.
Key benefits:
- Full ownership of component code
- Built on Radix UI primitives for accessibility
- Styled with Tailwind CSS
- TypeScript support
- Copy, paste, and customize
Project Structure
packages/frontend-common/
├── components.json # shadcn configuration
├── components/
│ └── ui/ # shadcn components
│ ├── alert.tsx
│ ├── badge.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── dialog.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── popover.tsx
│ ├── select.tsx
│ ├── textarea.tsx
│ ├── theme-toggle.tsx
│ └── index.ts
├── lib/
│ └── utils.ts # cn() helper function
└── styles/
└── theme.css # Theme variables for shadcnAdding New Components
To add a new shadcn component, navigate to the frontend-common package and run the shadcn CLI:
cd packages/frontend-common
bunx --bun shadcn@latest add [component-name]Examples
Add a single component:
cd packages/frontend-common
bunx --bun shadcn@latest add buttonAdd multiple components:
cd packages/frontend-common
bunx --bun shadcn@latest add button card dialogAdd all available components:
cd packages/frontend-common
bunx --bun shadcn@latest add --allThe CLI will:
- Download the component from the shadcn registry
- Place it in
components/ui/ - Install any required dependencies
- Update imports automatically
After Adding Components
After adding new components, export them from packages/frontend-common/components/ui/index.ts:
// packages/frontend-common/components/ui/index.ts
export * from "./alert";
export * from "./badge";
export * from "./button";
// ... add your new component here
export * from "./new-component";This ensures the component is available through the package's public API.
Using Components
In Frontend App
Import shadcn components from frontend-common:
// apps/frontend/src/pages/Dashboard.tsx
import { Button, Card, Dialog } from "frontend-common/components/ui";
export function Dashboard() {
return (
<Card>
<Button>Click me</Button>
</Card>
);
}In Admin App
Same import pattern works in the admin app:
// apps/admin/src/pages/Users.tsx
import { Button, Badge, Input } from "frontend-common/components/ui";
export function Users() {
return (
<div>
<Input placeholder="Search users..." />
<Badge>Admin</Badge>
</div>
);
}Using the cn() Utility
The cn() utility function combines class names and handles Tailwind conflicts:
import { cn } from "frontend-common/lib";
<Button className={cn("mt-4", isActive && "bg-primary")} />Customizing Components
Since components are copied into your codebase, you can customize them directly:
Modify Existing Components
Edit components in packages/frontend-common/components/ui/:
// packages/frontend-common/components/ui/button.tsx
export const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
// Add your custom variant here
custom: "bg-gradient-to-r from-purple-500 to-pink-500 text-white",
},
// ...
},
}
);Create Custom Components
Create app-specific components that use shadcn primitives:
// apps/frontend/src/components/FeatureCard.tsx
import { Card } from "frontend-common/components/ui";
export function FeatureCard({ title, description }: Props) {
return (
<Card className="p-6">
<h3 className="text-lg font-bold">{title}</h3>
<p className="text-muted-foreground">{description}</p>
</Card>
);
}Theme Configuration
Theme variables are defined in packages/frontend-common/styles/theme.css using CSS custom properties:
:root {
--radius: 0.6rem;
--background: oklch(0.985 0.008 95);
--foreground: oklch(0.21 0.02 248);
--primary: oklch(0.62 0.17 238);
/* ... more variables */
}
.dark {
--background: oklch(0.17 0.02 248);
--foreground: oklch(0.97 0.01 240);
/* ... dark mode overrides */
}Customizing Colors
To change the color scheme:
- Edit
packages/frontend-common/styles/theme.css - Modify the CSS variables for both light and dark modes
- Changes will apply to all shadcn components across both apps
Border Radius
Adjust the global border radius:
:root {
--radius: 0.5rem; /* Change this value */
}Available Components
Current shadcn components in the project:
- Alert - Display important messages
- Badge - Small status indicators
- Button - Clickable buttons with variants
- Card - Container for content
- Container - Layout wrapper
- Dialog - Modal dialogs
- Dropdown Menu - Contextual menus
- Input - Text input fields
- Label - Form labels
- Link - Styled links
- Popover - Floating content
- Select - Dropdown selection
- Styled Link - Enhanced link component
- Textarea - Multi-line text input
- Theme Toggle - Dark/light mode switcher
Browse all available shadcn components at ui.shadcn.com/docs/components.
Configuration
The packages/frontend-common/components.json file configures how shadcn components are installed:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "styles/theme.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@frontend-common/components",
"utils": "@frontend-common/lib/utils",
"ui": "@frontend-common/components/ui",
"lib": "@frontend-common/lib",
"hooks": "@frontend-common/hooks"
},
"iconLibrary": "lucide"
}Key settings:
style: "new-york"- Uses the New York design styletsx: true- Components use TypeScriptcssVariables: true- Uses CSS variables for themingiconLibrary: "lucide"- Uses Lucide icons
Troubleshooting
Component not found after adding
Make sure you've exported the component in packages/frontend-common/components/ui/index.ts.
Import errors in apps
Ensure you're importing from frontend-common/components/ui:
// ✅ Correct
import { Button } from "frontend-common/components/ui";
// ❌ Wrong
import { Button } from "@frontend-common/components/ui";Styles not applying
- Verify
frontend-common/styles/theme.cssis imported in your app's CSS - Check that Tailwind is scanning the
frontend-commonpackage - Ensure CSS variables are defined in
theme.css
Path alias issues
The @frontend-common/* alias in components.json points to the frontend-common package root. If you encounter path issues, check tsconfig.json:
{
"compilerOptions": {
"paths": {
"@frontend-common/*": ["./*"]
}
}
}Resources
Best Practices
- Always run shadcn CLI from frontend-common - This ensures components are added to the shared package
- Export new components - Add exports to
ui/index.tsafter adding components - Customize in one place - Edit components in
frontend-common, not in individual apps - Use the cn() utility - For conditional classes and Tailwind merging
- Follow the theme system - Use CSS variables instead of hardcoded colors
- Test in both apps - Verify components work in both frontend and admin
- Check Storybook - Add stories for new components to showcase them
Example Workflow
Complete workflow for adding and using a new component:
# 1. Navigate to frontend-common
cd packages/frontend-common
# 2. Add the component
bunx --bun shadcn@latest add avatar
# 3. Export it (if not auto-exported)
# Edit packages/frontend-common/components/ui/index.ts
# Add: export * from "./avatar";
# 4. Use it in your app
# apps/frontend/src/components/UserProfile.tsximport { Avatar } from "frontend-common/components/ui";
export function UserProfile({ user }) {
return (
<div>
<Avatar>
<img src={user.avatar} alt={user.name} />
</Avatar>
<span>{user.name}</span>
</div>
);
}# 5. (Optional) Add a Storybook story
# apps/storybook/src/Avatar.stories.tsx