StarterApp Docs
Packages

@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

Button variants
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

Input with validation
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

Textarea for long-form input
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

Card structure
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

Sheet for navigation or forms
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

Accordion for FAQs or documentation
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

Toast notifications
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 feedback
  • toast.error(message) - Error feedback
  • toast.info(message) - Informational message
  • toast.warning(message) - Warning message

Badge

Status badges
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

Theme provider setup
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 detection
  • disableTransitionOnChange: Skip CSS transitions on theme change

ThemeToggle

Theme toggle button
import { ThemeToggle } from "@workspace/ui/components/theme-toggle";

export function Header() {
  return (
    <nav>
      <ThemeToggle />
    </nav>
  );
}

Special Components

PricingPlanGrid

Pricing table from billing constants
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.

Merging class names
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 variable structure
@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.

Server component usage
// 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

Direct component imports
// ✅ 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:

Custom styling
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:

Conditional styling
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.