@workspace/ui
Shared UI components and Tailwind utilities
Design system with accessible components built on Radix UI and Tailwind CSS v4. Components support Server Components, dark mode, and keyboard navigation.
AI-Assisted Component Usage
AI assistants can generate component implementations from natural language descriptions. Describe the interface, and the AI selects appropriate components with proper accessibility patterns.
Package Structure
Core Components
Form Elements
Button
Primary action trigger with variant support
Input
Text input with validation states
Textarea
Multi-line text input
Label
Form field labels with proper associations
Button
import { Button } from "@workspace/ui/components/button";
export function Actions() {
return (
<>
<Button variant="default">Save Changes</Button>
<Button variant="destructive">Delete Account</Button>
<Button variant="outline">Cancel</Button>
<Button variant="ghost">Skip</Button>
</>
);
}
Props
variant
:"default" | "destructive" | "outline" | "ghost"
size
:"default" | "sm" | "lg" | "icon"
- All native
<button>
props
Input
import { Input } from "@workspace/ui/components/input";
import { Label } from "@workspace/ui/components/label";
export function EmailField() {
return (
<div>
<Label htmlFor="email">Email address</Label>
<Input
id="email"
type="email"
placeholder="you@example.com"
required
/>
</div>
);
}
Textarea
import { Textarea } from "@workspace/ui/components/textarea";
import { Label } from "@workspace/ui/components/label";
export function MessageField() {
return (
<div>
<Label htmlFor="message">Your message</Label>
<Textarea
id="message"
placeholder="Describe your issue..."
rows={5}
/>
</div>
);
}
Layout Components
Card
Content container with header, body, footer sections
Section
Semantic section wrapper with spacing
Separator
Visual divider between content
Card
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "@workspace/ui/components/card";
import { Button } from "@workspace/ui/components/button";
export function ProfileCard() {
return (
<Card>
<CardHeader>
<CardTitle>Account Settings</CardTitle>
<CardDescription>
Manage your profile and preferences
</CardDescription>
</CardHeader>
<CardContent>
{/* Form fields */}
</CardContent>
<CardFooter>
<Button>Save Changes</Button>
</CardFooter>
</Card>
);
}
Overlay Components
Sheet
Slide-in panel for secondary content
Accordion
Collapsible content sections
Tooltip
Contextual help on hover
Sheet
import {
Sheet,
SheetTrigger,
SheetContent,
SheetHeader,
SheetTitle,
SheetDescription,
} from "@workspace/ui/components/sheet";
import { Button } from "@workspace/ui/components/button";
export function SettingsSheet() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Settings</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Settings</SheetTitle>
<SheetDescription>
Configure your preferences
</SheetDescription>
</SheetHeader>
{/* Settings form */}
</SheetContent>
</Sheet>
);
}
Accordion
import {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionContent,
} from "@workspace/ui/components/accordion";
export function FAQ() {
return (
<Accordion type="single" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger>What is included?</AccordionTrigger>
<AccordionContent>
All features listed in your plan.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>How does billing work?</AccordionTrigger>
<AccordionContent>
Monthly or annual billing cycles.
</AccordionContent>
</AccordionItem>
</Accordion>
);
}
Feedback Components
Sonner
import { Toaster, toast } from "@workspace/ui/components/sonner";
export function App({ children }: { children: React.ReactNode }) {
return (
<>
{children}
<Toaster />
</>
);
}
// Trigger toasts
export function NotificationDemo() {
return (
<button onClick={() => toast.success("Changes saved")}>
Save
</button>
);
}
Toast Types
toast.success(message)
- Success feedbacktoast.error(message)
- Error feedbacktoast.info(message)
- Informational messagetoast.warning(message)
- Warning message
Badge
import { Badge } from "@workspace/ui/components/badge";
export function StatusIndicator({ status }: { status: string }) {
return (
<>
<Badge variant="default">Active</Badge>
<Badge variant="secondary">Pending</Badge>
<Badge variant="destructive">Cancelled</Badge>
<Badge variant="outline">Draft</Badge>
</>
);
}
Theme Components
ThemeProvider
System and manual theme switching
ThemeToggle
UI control for theme selection
ThemeSwitcher
Dropdown theme selector
ThemeProvider
import { ThemeProvider } from "@workspace/ui/hooks/use-theme";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
);
}
Props
attribute
:"class"
(applies theme via class name)defaultTheme
:"light" | "dark" | "system"
enableSystem
: Allow system preference detectiondisableTransitionOnChange
: Skip CSS transitions on theme change
ThemeToggle
import { ThemeToggle } from "@workspace/ui/components/theme-toggle";
export function Header() {
return (
<nav>
<ThemeToggle />
</nav>
);
}
Special Components
PricingPlanGrid
import { PricingPlanGrid } from "@workspace/ui/components/pricing-plan-grid";
import { BILLING_PLAN_DETAILS } from "@workspace/billing";
export function PricingPage() {
return (
<PricingPlanGrid
plans={BILLING_PLAN_DETAILS}
onSelectPlan={(productId) => {
// Handle plan selection
}}
/>
);
}
Utility Functions
cn()
Class name merger with Tailwind conflict resolution.
import { cn } from "@workspace/ui/lib/utils";
export function ConditionalButton({ isActive }: { isActive: boolean }) {
return (
<button
className={cn(
"px-4 py-2 rounded",
isActive && "bg-blue-500 text-white",
!isActive && "bg-gray-200 text-gray-700"
)}
>
Click me
</button>
);
}
The cn()
function uses clsx
and tailwind-merge
to handle:
- Conditional class names
- Tailwind class conflicts (last wins)
- Null/undefined filtering
Theme System
CSS Custom Properties
Themes define semantic color tokens in globals.css
:
@theme {
--color-background: oklch(100% 0 0);
--color-foreground: oklch(0% 0 0);
--color-primary: oklch(47.21% 0.246 264.05);
--color-destructive: oklch(56.91% 0.247 23.11);
/* ... */
}
@media (prefers-color-scheme: dark) {
@theme {
--color-background: oklch(0% 0 0);
--color-foreground: oklch(100% 0 0);
/* ... */
}
}
Components reference these variables for consistent theming.
Accessibility Features
All components include:
- Keyboard navigation - Tab, Enter, Escape, Arrow keys
- Screen reader support - ARIA labels, roles, live regions
- Focus management - Visible focus indicators, focus trapping in modals
- Motion preferences - Respects
prefers-reduced-motion
Heads up
Components handle accessibility automatically. Avoid overriding ARIA attributes or focus behavior unless required for custom interactions.
Server Component Compatibility
All components support React Server Components. Client-only components (using hooks or event handlers) are marked with "use client"
directive.
// This runs on the server
import { Card, CardHeader, CardTitle, CardContent } from "@workspace/ui/components/card";
export default async function Dashboard() {
const data = await fetchData();
return (
<Card>
<CardHeader>
<CardTitle>Dashboard</CardTitle>
</CardHeader>
<CardContent>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</CardContent>
</Card>
);
}
Interactive components like Sheet
, ThemeToggle
, and Sonner
use "use client"
internally but can be imported in Server Components.
Import Patterns
// ✅ Preferred - enables tree shaking
import { Button } from "@workspace/ui/components/button";
import { Card } from "@workspace/ui/components/card";
// ❌ Avoid - imports entire package
import { Button, Card } from "@workspace/ui";
Direct imports reduce bundle size by excluding unused components.
Styling Customization
Components accept className
prop for Tailwind overrides:
import { Button } from "@workspace/ui/components/button";
export function CustomButton() {
return (
<Button className="bg-purple-500 hover:bg-purple-600">
Custom Color
</Button>
);
}
Use cn()
when combining with conditional classes:
import { Button } from "@workspace/ui/components/button";
import { cn } from "@workspace/ui/lib/utils";
export function StatusButton({ isPrimary }: { isPrimary: boolean }) {
return (
<Button
className={cn(
isPrimary && "bg-blue-600",
!isPrimary && "bg-gray-600"
)}
>
Submit
</Button>
);
}
Component Dependencies
The UI package uses:
- Radix UI - Accessible component primitives
- Tailwind CSS v4 - Utility-first styling
- class-variance-authority - Variant management
- tailwind-merge - Class conflict resolution
- Lucide React - Icon library
No additional dependencies required in consuming applications.