GameCraftGameCraft

UI Components

Complete reference for ProductReady's UI component library (KUI)

UI Components

ProductReady uses KUI - a comprehensive UI component library built on shadcn/ui with additional enhancements. All components are:

  • Type-safe - Full TypeScript support
  • Accessible - WCAG 2.1 AA compliant
  • Themeable - CSS variables for customization
  • Dark mode - Built-in dark mode support
  • Tree-shakeable - Import only what you need

Location: All components live in packages/kui/src/components/ui/


Quick Import

import { Button } from 'kui/button';
import { Card } from 'kui/card';
import { Input } from 'kui/input';

export function MyComponent() {
  return (
    <Card>
      <Input placeholder="Enter text" />
      <Button>Submit</Button>
    </Card>
  );
}

Component Categories

Form Components

import { Input } from 'kui/input';

// Basic input
<Input placeholder="Email" type="email" />

// With label
<div>
  <Label htmlFor="email">Email</Label>
  <Input id="email" type="email" />
</div>

// Disabled
<Input disabled placeholder="Disabled" />
import { Textarea } from 'kui/textarea';

<Textarea placeholder="Your message..." rows={4} />
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'kui/select';

<Select>
  <SelectTrigger>
    <SelectValue placeholder="Select option" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="option1">Option 1</SelectItem>
    <SelectItem value="option2">Option 2</SelectItem>
  </SelectContent>
</Select>
import { Checkbox } from 'kui/checkbox';
import { Label } from 'kui/label';

<div className="flex items-center gap-2">
  <Checkbox id="terms" />
  <Label htmlFor="terms">Accept terms</Label>
</div>

Available Form Components:

  • Input - Text input with variants
  • Textarea - Multi-line text input
  • Select - Dropdown select
  • Checkbox - Checkbox with label
  • Radio Group - Radio button group
  • Switch - Toggle switch
  • Slider - Range slider
  • Calendar - Date picker
  • Input OTP - One-time password input
  • Form - Form wrapper with validation

Layout Components

Card

import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'kui/card';

<Card>
  <CardHeader>
    <CardTitle>Card Title</CardTitle>
    <CardDescription>Card description goes here</CardDescription>
  </CardHeader>
  <CardContent>
    <p>Card content</p>
  </CardContent>
  <CardFooter>
    <Button>Action</Button>
  </CardFooter>
</Card>

Separator

import { Separator } from 'kui/separator';

<div>
  <p>Content above</p>
  <Separator className="my-4" />
  <p>Content below</p>
</div>

// Vertical separator
<Separator orientation="vertical" className="h-4" />

Tabs

import { Tabs, TabsContent, TabsList, TabsTrigger } from 'kui/tabs';

<Tabs defaultValue="tab1">
  <TabsList>
    <TabsTrigger value="tab1">Tab 1</TabsTrigger>
    <TabsTrigger value="tab2">Tab 2</TabsTrigger>
  </TabsList>
  <TabsContent value="tab1">Content 1</TabsContent>
  <TabsContent value="tab2">Content 2</TabsContent>
</Tabs>

Accordion

import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from 'kui/accordion';

<Accordion type="single" collapsible>
  <AccordionItem value="item-1">
    <AccordionTrigger>Is it accessible?</AccordionTrigger>
    <AccordionContent>
      Yes. It adheres to the WAI-ARIA design pattern.
    </AccordionContent>
  </AccordionItem>
</Accordion>

Buttons & Actions

Button

import { Button } from 'kui/button';

// Variants
<Button variant="default">Default</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>

// Sizes
<Button size="default">Default</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
<Button size="icon"><Icon /></Button>

// States
<Button disabled>Disabled</Button>
<Button loading>Loading...</Button>

Button Group

import { ButtonGroup } from 'kui/button-group';

<ButtonGroup>
  <Button>Left</Button>
  <Button>Center</Button>
  <Button>Right</Button>
</ButtonGroup>

Toggle

import { Toggle } from 'kui/toggle';

<Toggle aria-label="Toggle italic">
  <Italic className="h-4 w-4" />
</Toggle>

Overlay Components

Dialog

import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from 'kui/dialog';

<Dialog>
  <DialogTrigger asChild>
    <Button>Open Dialog</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Dialog Title</DialogTitle>
      <DialogDescription>Dialog description</DialogDescription>
    </DialogHeader>
    <p>Dialog content goes here</p>
  </DialogContent>
</Dialog>

Drawer

import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, DrawerTrigger } from 'kui/drawer';

<Drawer>
  <DrawerTrigger>Open Drawer</DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Drawer Title</DrawerTitle>
      <DrawerDescription>Drawer description</DrawerDescription>
    </DrawerHeader>
  </DrawerContent>
</Drawer>

Sheet

import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from 'kui/sheet';

<Sheet>
  <SheetTrigger>Open Sheet</SheetTrigger>
  <SheetContent side="right">
    <SheetHeader>
      <SheetTitle>Sheet Title</SheetTitle>
      <SheetDescription>Sheet description</SheetDescription>
    </SheetHeader>
  </SheetContent>
</Sheet>

Popover

import { Popover, PopoverContent, PopoverTrigger } from 'kui/popover';

<Popover>
  <PopoverTrigger>Open Popover</PopoverTrigger>
  <PopoverContent>
    <p>Popover content</p>
  </PopoverContent>
</Popover>

Tooltip

import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'kui/tooltip';

<TooltipProvider>
  <Tooltip>
    <TooltipTrigger>Hover me</TooltipTrigger>
    <TooltipContent>
      <p>Tooltip text</p>
    </TooltipContent>
  </Tooltip>
</TooltipProvider>

Feedback Components

Alert

import { Alert, AlertDescription, AlertTitle } from 'kui/alert';
import { AlertCircle } from 'lucide-react';

<Alert>
  <AlertCircle className="h-4 w-4" />
  <AlertTitle>Heads up!</AlertTitle>
  <AlertDescription>
    This is an alert message.
  </AlertDescription>
</Alert>

// Variants
<Alert variant="default">Default Alert</Alert>
<Alert variant="destructive">Error Alert</Alert>

Toast (Sonner)

import { toast } from 'sonner';

// Success
toast.success('Task created successfully!');

// Error
toast.error('Something went wrong');

// Info
toast.info('New update available');

// Custom
toast('Custom message', {
  description: 'With description',
  action: {
    label: 'Undo',
    onClick: () => console.log('Undo'),
  },
});

Progress

import { Progress } from 'kui/progress';

<Progress value={60} /> // 60%

Spinner

import { Spinner } from 'kui/spinner';

<Spinner size="sm" />
<Spinner size="default" />
<Spinner size="lg" />

Skeleton

import { Skeleton } from 'kui/skeleton';

<div className="space-y-2">
  <Skeleton className="h-4 w-full" />
  <Skeleton className="h-4 w-3/4" />
</div>

Data Display

Table

import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'kui/table';

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Email</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>John Doe</TableCell>
      <TableCell>john@example.com</TableCell>
    </TableRow>
  </TableBody>
</Table>

Badge

import { Badge } from 'kui/badge';

<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>

Avatar

import { Avatar, AvatarFallback, AvatarImage } from 'kui/avatar';

<Avatar>
  <AvatarImage src="/avatar.jpg" alt="User" />
  <AvatarFallback>JD</AvatarFallback>
</Avatar>

Stats Card

import { StatsCard } from 'kui/stats-card';

<StatsCard
  title="Total Users"
  value="1,234"
  change="+12%"
  trend="up"
/>

Chart

import { Chart } from 'kui/chart';

<Chart
  data={chartData}
  type="line"
  xKey="date"
  yKey="value"
/>

Breadcrumb

import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from 'kui/breadcrumb';

<Breadcrumb>
  <BreadcrumbList>
    <BreadcrumbItem>
      <BreadcrumbLink href="/">Home</BreadcrumbLink>
    </BreadcrumbItem>
    <BreadcrumbSeparator />
    <BreadcrumbItem>
      <BreadcrumbPage>Current Page</BreadcrumbPage>
    </BreadcrumbItem>
  </BreadcrumbList>
</Breadcrumb>

Sidebar

import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from 'kui/sidebar';

<Sidebar>
  <SidebarContent>
    <SidebarGroup>
      <SidebarGroupLabel>Navigation</SidebarGroupLabel>
      <SidebarGroupContent>
        <SidebarMenu>
          <SidebarMenuItem>
            <SidebarMenuButton asChild>
              <a href="/dashboard">Dashboard</a>
            </SidebarMenuButton>
          </SidebarMenuItem>
        </SidebarMenu>
      </SidebarGroupContent>
    </SidebarGroup>
  </SidebarContent>
</Sidebar>

Menubar

import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarTrigger } from 'kui/menubar';

<Menubar>
  <MenubarMenu>
    <MenubarTrigger>File</MenubarTrigger>
    <MenubarContent>
      <MenubarItem>New File</MenubarItem>
      <MenubarItem>Save</MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>

Pagination

import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from 'kui/pagination';

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious href="#" />
    </PaginationItem>
    <PaginationItem>
      <PaginationLink href="#">1</PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationNext href="#" />
    </PaginationItem>
  </PaginationContent>
</Pagination>

Special Components

Command Palette

import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from 'kui/command';

<Command>
  <CommandInput placeholder="Type a command..." />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Suggestions">
      <CommandItem>Dashboard</CommandItem>
      <CommandItem>Settings</CommandItem>
    </CommandGroup>
  </CommandList>
</Command>
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from 'kui/carousel';

<Carousel>
  <CarouselContent>
    <CarouselItem>Slide 1</CarouselItem>
    <CarouselItem>Slide 2</CarouselItem>
    <CarouselItem>Slide 3</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

Context Menu

import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from 'kui/context-menu';

<ContextMenu>
  <ContextMenuTrigger>Right click me</ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuItem>Edit</ContextMenuItem>
    <ContextMenuItem>Delete</ContextMenuItem>
  </ContextMenuContent>
</ContextMenu>

Complete Component List

ComponentDescriptionCategory
accordionCollapsible content sectionsLayout
alert-dialogModal confirmation dialogOverlay
alertFeedback messagesFeedback
aspect-ratioMaintain aspect ratioLayout
avatarUser profile imageData Display
badgeStatus indicatorsData Display
breadcrumbNavigation breadcrumbsNavigation
button-groupGrouped buttonsActions
buttonClickable buttonActions
calendarDate pickerForm
cardContent containerLayout
carouselImage/content sliderSpecial
chartData visualizationData Display
checkboxCheckbox inputForm
collapsibleExpandable contentLayout
commandCommand paletteSpecial
context-menuRight-click menuNavigation
dialogModal dialogOverlay
drawerSide panelOverlay
dropdown-menuDropdown menuNavigation
emptyEmpty stateFeedback
fieldForm field wrapperForm
formForm containerForm
hover-cardHover preview cardOverlay
input-groupGrouped inputsForm
input-otpOTP inputForm
inputText inputForm
itemList itemData Display
kbdKeyboard shortcutData Display
labelInput labelForm
menubarMenu barNavigation
navigation-menuNavigation menuNavigation
paginationPage navigationNavigation
popoverFloating contentOverlay
progressProgress barFeedback
radio-groupRadio buttonsForm
resizableResizable panelsLayout
scroll-areaScrollable areaLayout
selectDropdown selectForm
separatorDivider lineLayout
sheetSide sheetOverlay
sidebarApp sidebarNavigation
skeletonLoading placeholderFeedback
sliderRange sliderForm
sonnerToast notificationsFeedback
spinnerLoading spinnerFeedback
stats-cardMetrics cardData Display
switchToggle switchForm
table-list-viewTable/list viewData Display
tableData tableData Display
tabsTabbed contentLayout
textareaMulti-line inputForm
toggle-groupToggle button groupActions
toggleToggle buttonActions
tooltipHover tooltipOverlay

Customization

Theme Colors

Edit apps/productready/src/app/globals.css:

@layer base {
  :root {
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    /* ... more variables */
  }
  
  .dark {
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    /* ... more variables */
  }
}

Component Variants

Extend component variants:

// Create custom button variant
<Button className="bg-purple-600 hover:bg-purple-700">
  Custom Color
</Button>

Best Practices

✅ Do

  • Use semantic HTML - Components render proper HTML
  • Import only needed - Tree-shaking optimizes bundle
  • Follow accessibility - Use labels, ARIA attributes
  • Compose components - Build complex UIs from simple parts
  • Test dark mode - All components support dark mode

❌ Don't

  • Don't modify KUI directly - Create wrapper components instead
  • Don't skip labels - Always label form inputs
  • Don't inline styles - Use className with Tailwind
  • Don't forget loading states - Show spinners for async actions

Common Patterns & Real-World Examples

User Profile Card

Combine multiple components for a complete user profile card:

import { Card, CardContent, CardFooter, CardHeader } from 'kui/card';
import { Avatar, AvatarFallback, AvatarImage } from 'kui/avatar';
import { Button } from 'kui/button';
import { Badge } from 'kui/badge';

export function UserProfileCard({ user }: { user: User }) {
  return (
    <Card className="w-[350px]">
      <CardHeader className="flex flex-row items-center gap-4">
        <Avatar className="h-12 w-12">
          <AvatarImage src={user.avatar} alt={user.name} />
          <AvatarFallback>{user.name.slice(0, 2).toUpperCase()}</AvatarFallback>
        </Avatar>
        <div className="flex flex-col">
          <h3 className="font-semibold">{user.name}</h3>
          <p className="text-sm text-muted-foreground">{user.email}</p>
        </div>
      </CardHeader>
      <CardContent>
        <div className="flex gap-2 flex-wrap">
          <Badge variant="secondary">Pro Member</Badge>
          <Badge variant="outline">Active</Badge>
        </div>
        <p className="mt-4 text-sm">{user.bio}</p>
      </CardContent>
      <CardFooter className="flex gap-2">
        <Button variant="default" className="flex-1">Message</Button>
        <Button variant="outline" className="flex-1">Follow</Button>
      </CardFooter>
    </Card>
  );
}

Settings Form with Validation

Complete form example with multiple input types:

'use client';

import { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'kui/card';
import { Input } from 'kui/input';
import { Label } from 'kui/label';
import { Switch } from 'kui/switch';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'kui/select';
import { Button } from 'kui/button';
import { Separator } from 'kui/separator';
import { toast } from 'sonner';

export function SettingsForm() {
  const [settings, setSettings] = useState({
    name: '',
    email: '',
    theme: 'light',
    notifications: true,
  });

  const handleSave = () => {
    // Save logic here
    toast.success('Settings saved successfully!');
  };

  return (
    <Card className="max-w-2xl">
      <CardHeader>
        <CardTitle>Account Settings</CardTitle>
        <CardDescription>Manage your account preferences</CardDescription>
      </CardHeader>
      <CardContent className="space-y-6">
        <div className="space-y-2">
          <Label htmlFor="name">Display Name</Label>
          <Input 
            id="name" 
            placeholder="Enter your name"
            value={settings.name}
            onChange={(e) => setSettings({ ...settings, name: e.target.value })}
          />
        </div>

        <div className="space-y-2">
          <Label htmlFor="email">Email Address</Label>
          <Input 
            id="email" 
            type="email"
            placeholder="your@email.com"
            value={settings.email}
            onChange={(e) => setSettings({ ...settings, email: e.target.value })}
          />
        </div>

        <Separator />

        <div className="space-y-2">
          <Label htmlFor="theme">Theme</Label>
          <Select 
            value={settings.theme}
            onValueChange={(value) => setSettings({ ...settings, theme: value })}
          >
            <SelectTrigger id="theme">
              <SelectValue placeholder="Select theme" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="light">Light</SelectItem>
              <SelectItem value="dark">Dark</SelectItem>
              <SelectItem value="system">System</SelectItem>
            </SelectContent>
          </Select>
        </div>

        <div className="flex items-center justify-between">
          <div className="space-y-0.5">
            <Label>Email Notifications</Label>
            <p className="text-sm text-muted-foreground">
              Receive emails about account activity
            </p>
          </div>
          <Switch
            checked={settings.notifications}
            onCheckedChange={(checked) => setSettings({ ...settings, notifications: checked })}
          />
        </div>

        <div className="flex gap-2 justify-end">
          <Button variant="outline">Cancel</Button>
          <Button onClick={handleSave}>Save Changes</Button>
        </div>
      </CardContent>
    </Card>
  );
}

Data Table with Actions

Interactive table with row actions:

import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'kui/table';
import { Badge } from 'kui/badge';
import { Button } from 'kui/button';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from 'kui/dropdown-menu';
import { MoreHorizontal, Pencil, Trash } from 'lucide-react';

export function UsersTable({ users }: { users: User[] }) {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Name</TableHead>
          <TableHead>Email</TableHead>
          <TableHead>Status</TableHead>
          <TableHead>Role</TableHead>
          <TableHead className="text-right">Actions</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {users.map((user) => (
          <TableRow key={user.id}>
            <TableCell className="font-medium">{user.name}</TableCell>
            <TableCell>{user.email}</TableCell>
            <TableCell>
              <Badge variant={user.active ? 'default' : 'secondary'}>
                {user.active ? 'Active' : 'Inactive'}
              </Badge>
            </TableCell>
            <TableCell>{user.role}</TableCell>
            <TableCell className="text-right">
              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <Button variant="ghost" size="icon">
                    <MoreHorizontal className="h-4 w-4" />
                  </Button>
                </DropdownMenuTrigger>
                <DropdownMenuContent align="end">
                  <DropdownMenuItem>
                    <Pencil className="mr-2 h-4 w-4" />
                    Edit
                  </DropdownMenuItem>
                  <DropdownMenuItem className="text-destructive">
                    <Trash className="mr-2 h-4 w-4" />
                    Delete
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}

Confirmation Dialog

Delete confirmation with proper UX:

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from 'kui/alert-dialog';
import { Button } from 'kui/button';
import { Trash } from 'lucide-react';

export function DeleteButton({ itemName, onDelete }: { itemName: string; onDelete: () => void }) {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button variant="destructive" size="sm">
          <Trash className="mr-2 h-4 w-4" />
          Delete
        </Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
          <AlertDialogDescription>
            This action cannot be undone. This will permanently delete "{itemName}"
            and remove the data from our servers.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction onClick={onDelete} className="bg-destructive text-destructive-foreground">
            Delete
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  );
}

Dashboard Stats Cards

Display metrics with stats cards:

import { Card, CardContent, CardHeader, CardTitle } from 'kui/card';
import { ArrowUp, ArrowDown, Users, DollarSign, Activity, TrendingUp } from 'lucide-react';

export function DashboardStats() {
  const stats = [
    { title: 'Total Revenue', value: '$45,231', change: '+20.1%', icon: DollarSign, positive: true },
    { title: 'Active Users', value: '2,345', change: '+12%', icon: Users, positive: true },
    { title: 'Conversion Rate', value: '3.24%', change: '-2.4%', icon: TrendingUp, positive: false },
    { title: 'API Calls', value: '1.2M', change: '+45%', icon: Activity, positive: true },
  ];

  return (
    <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
      {stats.map((stat) => (
        <Card key={stat.title}>
          <CardHeader className="flex flex-row items-center justify-between pb-2">
            <CardTitle className="text-sm font-medium text-muted-foreground">
              {stat.title}
            </CardTitle>
            <stat.icon className="h-4 w-4 text-muted-foreground" />
          </CardHeader>
          <CardContent>
            <div className="text-2xl font-bold">{stat.value}</div>
            <p className={`text-xs flex items-center gap-1 ${stat.positive ? 'text-green-600' : 'text-red-600'}`}>
              {stat.positive ? <ArrowUp className="h-3 w-3" /> : <ArrowDown className="h-3 w-3" />}
              {stat.change} from last month
            </p>
          </CardContent>
        </Card>
      ))}
    </div>
  );
}

Loading States & Skeletons

Show loading placeholders:

import { Card, CardContent, CardHeader } from 'kui/card';
import { Skeleton } from 'kui/skeleton';

export function UserCardSkeleton() {
  return (
    <Card>
      <CardHeader className="flex flex-row items-center gap-4">
        <Skeleton className="h-12 w-12 rounded-full" />
        <div className="space-y-2 flex-1">
          <Skeleton className="h-4 w-[200px]" />
          <Skeleton className="h-3 w-[150px]" />
        </div>
      </CardHeader>
      <CardContent>
        <Skeleton className="h-20 w-full" />
      </CardContent>
    </Card>
  );
}

// Usage in component
export function UsersList() {
  const { data: users, isLoading } = trpc.users.list.useQuery();

  if (isLoading) {
    return (
      <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
        {Array.from({ length: 6 }).map((_, i) => (
          <UserCardSkeleton key={i} />
        ))}
      </div>
    );
  }

  return (
    <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
      {users.map((user) => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

Quick navigation with command palette:

'use client';

import { useState, useEffect } from 'react';
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from 'kui/command';
import { Search, FileText, Settings, Users, DollarSign } from 'lucide-react';

export function CommandPalette() {
  const [open, setOpen] = useState(false);

  useEffect(() => {
    const down = (e: KeyboardEvent) => {
      if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
        e.preventDefault();
        setOpen((open) => !open);
      }
    };
    document.addEventListener('keydown', down);
    return () => document.removeEventListener('keydown', down);
  }, []);

  return (
    <CommandDialog open={open} onOpenChange={setOpen}>
      <CommandInput placeholder="Type a command or search..." />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup heading="Suggestions">
          <CommandItem onSelect={() => console.log('Navigate to Dashboard')}>
            <FileText className="mr-2 h-4 w-4" />
            <span>Dashboard</span>
          </CommandItem>
          <CommandItem onSelect={() => console.log('Navigate to Users')}>
            <Users className="mr-2 h-4 w-4" />
            <span>Users</span>
          </CommandItem>
          <CommandItem onSelect={() => console.log('Navigate to Billing')}>
            <DollarSign className="mr-2 h-4 w-4" />
            <span>Billing</span>
          </CommandItem>
        </CommandGroup>
        <CommandGroup heading="Settings">
          <CommandItem onSelect={() => console.log('Navigate to Settings')}>
            <Settings className="mr-2 h-4 w-4" />
            <span>Settings</span>
          </CommandItem>
        </CommandGroup>
      </CommandList>
    </CommandDialog>
  );
}

Component Composition Tips

Building Complex Forms

Combine form components effectively:

import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from 'kui/form';
import { Input } from 'kui/input';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';

const formSchema = z.object({
  username: z.string().min(3).max(20),
  email: z.string().email(),
});

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

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
        <FormField
          control={form.control}
          name="username"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Username</FormLabel>
              <FormControl>
                <Input placeholder="johndoe" {...field} />
              </FormControl>
              <FormDescription>This is your public display name.</FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Create Account</Button>
      </form>
    </Form>
  );
}

Responsive Layouts

Use cards and grids for responsive design:

<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
  {items.map((item) => (
    <Card key={item.id} className="hover:shadow-lg transition-shadow">
      <CardHeader>
        <CardTitle>{item.title}</CardTitle>
      </CardHeader>
      <CardContent>{item.content}</CardContent>
    </Card>
  ))}
</div>

Accessibility Tips

Keyboard Navigation

All KUI components support keyboard navigation:

  • Tab - Move between focusable elements
  • Enter/Space - Activate buttons and toggles
  • Arrow keys - Navigate menus, selects, radio groups
  • Escape - Close dialogs, popovers, sheets
  • Home/End - Navigate to first/last item in lists

Screen Reader Support

Components include ARIA attributes:

// Button with aria-label
<Button aria-label="Close dialog">
  <X className="h-4 w-4" />
</Button>

// Input with proper labeling
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" aria-describedby="email-description" />
<p id="email-description" className="text-sm text-muted-foreground">
  We'll never share your email.
</p>

Performance Optimization

Lazy Loading Components

For large applications, lazy load heavy components:

import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('~/components/heavy-chart'), {
  loading: () => <Skeleton className="h-[400px] w-full" />,
  ssr: false,
});

Memoization

Prevent unnecessary re-renders:

import { memo } from 'react';

export const UserCard = memo(function UserCard({ user }: { user: User }) {
  return (
    <Card>
      {/* Component content */}
    </Card>
  );
});

Next Steps


All components are production-ready and fully accessible! 🎨

For live component demonstrations and interactive examples, explore the ProductReady dashboard in development mode or check out the KUI Storybook (if available).

On this page