Skip to main content

UI Package

The @project-name/ui package is a shared component library built with shadcn/ui, providing a set of reusable, accessible, and customizable UI components for the web applications in the monorepo. All shadcn/ui components should be placed in this package to maintain a consistent component library across the project.

Overview

This package implements a collection of UI components following the shadcn/ui methodology:

  • Components are copied and customized directly in your codebase (not installed from npm)
  • Built on top of Radix UI primitives for accessibility and functionality
  • Styled with Tailwind CSS for consistent design
  • Fully customizable and adaptable to project requirements
  • Type-safe with TypeScript

Package Structure

packages/ui/
├── components.json # shadcn/ui configuration
├── package.json # Package dependencies and exports
└── src/
├── button.tsx # Button component
├── dropdown-menu.tsx # Dropdown menu components
├── form.tsx # Form components with react-hook-form
├── index.ts # Exports the cn utility
├── input.tsx # Input component
├── label.tsx # Label component
├── theme.tsx # Theme toggle and provider
└── toast.tsx # Toast notifications using sonner

Available Components

The UI package currently includes the following components:

Button

A versatile button component with various styles and sizes:

import { Button } from "@project-name/ui/button";

// Default button
<Button>Click me</Button>

// Button variants
<Button variant="destructive">Delete</Button>
<Button variant="outline">Cancel</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>

// Button sizes
<Button size="sm">Small</Button>
<Button size="md">Medium</Button> // Default
<Button size="lg">Large</Button>
<Button size="icon"><Icon /></Button>

A dropdown menu component based on Radix UI:

import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@project-name/ui/dropdown-menu";

<DropdownMenu>
<DropdownMenuTrigger>Open Menu</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>;

Form Components

Form components integrated with react-hook-form and zod validation:

import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@project-name/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@project-name/ui/form";
import { Input } from "@project-name/ui/input";
import { useForm } from "react-hook-form";
import { z } from "zod";

// Define schema
const formSchema = z.object({
email: z.string().email(),
});

// Create form
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
});

// Use in component
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>;

Input

A simple input component:

import { Input } from "@project-name/ui/input";

<Input placeholder="Enter text..." />
<Input type="email" disabled />

Label

A label component for form elements:

import { Label } from "@project-name/ui/label";

<Label htmlFor="email">Email</Label>;

Theme

Theme toggling and provider components:

import { ThemeProvider, ThemeToggle } from "@project-name/ui/theme";

// In your root layout
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
<ThemeToggle />
</ThemeProvider>;

Toast

Toast notifications using sonner:

import { toast, Toaster } from "@project-name/ui/toast";

// In your layout component
<Toaster />;

// Trigger toasts elsewhere
toast("Default toast");
toast.success("Success!");
toast.error("Something went wrong");
toast.warning("Warning message");

Theming

The UI package uses next-themes to handle theme switching with the following features:

  • Light and dark mode support
  • System preference detection
  • User preference persistence
  • CSS variables for theme colors

Theme configuration is managed through:

  1. ThemeProvider: Wraps your application to provide theme context
  2. ThemeToggle: A dropdown component to switch between themes
  3. Tailwind CSS: Theme styles defined in the Tailwind configuration
// How theming is applied in the root layout
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<html lang="en" suppressHydrationWarning>
<body className="min-h-screen bg-background font-sans antialiased">
{children}
<ThemeToggle />
</body>
</html>
</ThemeProvider>

Utilities

cn Function

The UI package exports a cn utility for merging Tailwind CSS classes with proper precedence:

import { cn } from "@project-name/ui";

// Merge Tailwind classes
<div className={cn("base-styles", isActive && "active-styles", className)}>
Content
</div>;

This function combines class-variance-authority with tailwind-merge to handle class conflicts and composition.

Adding New Components

New UI components can be added to the package using the shadcn CLI:

# From the repository root
pnpm ui-add

# Or specifying a component
pnpm dlx shadcn@latest add [component-name]
Important

All shadcn/ui components must be added to this package, rather than directly in individual apps. This ensures consistency across the monorepo and prevents duplication of UI code.

The CLI will:

  1. Add the component to src/
  2. Automatically update package.json exports
  3. Format the new component file

Manual Component Creation

To create a component manually:

  1. Create a new file in packages/ui/src/ (e.g., card.tsx)
  2. Implement the component using Radix UI primitives as needed
  3. Run pnpm update-exports to automatically update the exports in package.json, or manually add it to the exports field

Example of a manually created component:

// packages/ui/src/card.tsx
import * as React from "react";
import { cn } from "@project-name/ui";

export function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
className={cn("rounded-lg border bg-card p-4 shadow-sm", className)}
{...props}
/>
);
}

Using Components in Next.js

To use the UI components in a Next.js app:

// Import directly from the package path
import { Button } from "@project-name/ui/button";
import { Input } from "@project-name/ui/input";
import { ThemeProvider, ThemeToggle } from "@project-name/ui/theme";

// Use in your components
export default function MyComponent() {
return (
<div>
<h1>My Form</h1>
<Input placeholder="Enter value" />
<Button>Submit</Button>
</div>
);
}

Configuration

The UI package is configured through the components.json file:

{
"style": "new-york", // shadcn/ui style preset
"rsc": true, // Support for React Server Components
"tsx": true, // Use TypeScript with JSX
"tailwind": {
"config": "../../tooling/tailwind/web.ts",
"baseColor": "zinc",
"cssVariables": true
},
"aliases": {
"utils": "@project-name/ui",
"components": "src/",
"ui": "src/"
}
}

Dependencies

Key dependencies of the UI package:

  • Radix UI: Provides accessible primitives
  • class-variance-authority: For component variants
  • tailwind-merge: For merging Tailwind classes
  • next-themes: For theme management
  • react-hook-form: For form components
  • sonner: For toast notifications

This architecture provides a flexible and maintainable approach to UI components that can be easily extended and customized as your project grows.