shadcn/ui Component Library
Component inventory, generation workflow, customization approach, and the data-slot convention used by all UI primitives.
Trovella uses shadcn/ui as its component foundation. Components are generated into the project source tree at src/components/ui/, not installed as an npm dependency. This gives full ownership of the code -- components can be read, modified, and extended without version constraints.
Configuration
The components.json at the apps/web root configures the shadcn CLI:
{
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral"
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"hooks": "@/hooks",
"lib": "@/lib"
}
}
Key settings:
- Style:
new-york-- the more refined shadcn/ui style variant with smaller radii and tighter spacing - RSC:
true-- generated components are React Server Component compatible (though many use"use client"internally for interactivity) - Tailwind config is empty -- Tailwind v4 uses CSS-first configuration in
globals.css, not a config file - Icon library: Lucide -- all icon imports come from
lucide-react
Component Inventory
The 24 generated components in src/components/ui/:
Layout and Containers
| Component | File | Radix Primitive | Description |
|---|---|---|---|
| Card | card.tsx | None | Surface container with header, content, footer, and action slots |
| Dialog | dialog.tsx | Dialog | Modal overlay with animated open/close transitions |
| Sheet | sheet.tsx | Dialog | Slide-out panel (used for mobile sidebar) |
| Collapsible | collapsible.tsx | Collapsible | Expandable/collapsible content section |
| ScrollArea | scroll-area.tsx | ScrollArea | Custom-styled scrollbar container |
| Separator | separator.tsx | Separator | Horizontal or vertical divider line |
Navigation
| Component | File | Radix Primitive | Description |
|---|---|---|---|
| Sidebar | sidebar.tsx | None | Full sidebar system (provider, header, content, menu, groups) |
| Tabs | tabs.tsx | Tabs | Tabbed content with default and line variants |
| Breadcrumb | breadcrumb.tsx | None | Breadcrumb navigation trail |
| DropdownMenu | dropdown-menu.tsx | DropdownMenu | Context/action menu with keyboard navigation |
Forms and Inputs
| Component | File | Radix Primitive | Description |
|---|---|---|---|
| Button | button.tsx | None | Primary action element with 6 variants and 8 size options |
| Input | input.tsx | None | Text input field |
| Textarea | textarea.tsx | None | Multi-line text input |
| Label | label.tsx | Label | Form field label |
| Select | select.tsx | Select | Dropdown selection |
| Switch | switch.tsx | Switch | Toggle switch |
Data Display
| Component | File | Radix Primitive | Description |
|---|---|---|---|
| Table | table.tsx | None | Data table with header, body, footer, and caption |
| Badge | badge.tsx | None | Status indicator with 6 variants |
| Avatar | avatar.tsx | Avatar | User avatar with image and fallback |
| Skeleton | skeleton.tsx | None | Loading placeholder with pulse animation |
| Chart | chart.tsx | None | Recharts wrapper with theme-aware chart colors |
Feedback
| Component | File | Radix Primitive | Description |
|---|---|---|---|
| Alert | alert.tsx | None | Inline notification with icon, title, and description |
| Tooltip | tooltip.tsx | Tooltip | Hover/focus information popup |
| Sonner (Toaster) | sonner.tsx | None | Toast notification system with themed icons |
Component Patterns
The data-slot Convention
Every shadcn/ui component sets a data-slot attribute on its root element. This serves two purposes:
- CSS targeting -- parent components can style children using
has-data-[slot=...]selectors (used in Card headers to detect action slots) - Testing -- test selectors can target components by their semantic slot name rather than implementation-specific class names
// Card uses data-slot for layout detection
<div data-slot="card" className={cn("bg-card ...", className)} {...props} />
// CardHeader detects CardAction via has-data-[slot=card-action]
<div
data-slot="card-header"
className="has-data-[slot=card-action]:grid-cols-[1fr_auto] grid ..."
/>
CVA Variant Pattern
Components with multiple visual variants use class-variance-authority (CVA) to define type-safe variant maps:
const buttonVariants = cva("inline-flex items-center ...", {
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-white hover:bg-destructive/90",
outline: "border bg-background shadow-xs hover:bg-accent",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
xs: "h-6 gap-1 rounded-md px-2 text-xs",
sm: "h-8 gap-1.5 rounded-md px-3",
lg: "h-10 rounded-md px-6",
icon: "size-9",
"icon-xs": "size-6",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: { variant: "default", size: "default" },
});
Consumers pass variant and size as props, and CVA merges the correct classes. The buttonVariants export is also available for composing button-like elements (e.g., links styled as buttons).
The asChild Pattern
Many components support asChild via Radix Slot, allowing the component's styles to be applied to a different underlying element:
// Renders as a <button> (default)
<Button>Click me</Button>
// Renders as a <Link> with Button's styles
<Button asChild>
<Link href="/settings">Settings</Link>
</Button>
This is used extensively in the sidebar for navigation links that need menu button styling.
The Sidebar System
The sidebar is the most complex UI primitive, comprising 23 exported components and a context provider. Key architecture:
| Component | Role |
|---|---|
SidebarProvider | Context provider with cookie-persisted state and Cmd+B keyboard shortcut |
Sidebar | Main container -- renders as a fixed panel on desktop, a Sheet on mobile |
SidebarInset | Main content area adjacent to the sidebar |
SidebarMenu / SidebarMenuItem | Navigation list structure |
SidebarMenuButton | Nav item with active state, collapsed tooltip, and variant styling |
SidebarGroup / SidebarGroupLabel | Grouped navigation sections |
The sidebar has three collapsible modes: offcanvas (slides off screen), icon (collapses to icon-only rail), and none (always visible). Trovella uses icon mode, collapsing the sidebar from 16rem to 3rem wide.
Adding a New shadcn/ui Component
To add a component from the shadcn/ui registry:
cd apps/web
npx shadcn@latest add <component-name>
This generates the component file(s) into src/components/ui/ and installs any required Radix primitives. After generation:
- Review the generated code -- it becomes project-owned source
- Verify it uses
@/lib/utilsforcn()and@/components/ui/for internal dependencies - The component inherits the existing color tokens and does not introduce new Tailwind defaults
Customization Rules
Generated components may be modified, but follow these guidelines:
- Do not rename the file or exports -- other components import by the canonical name
- Preserve
data-slotattributes -- they enable CSS parent selectors and test targeting - Keep variant definitions in CVA -- do not scatter conditional class logic across the component
- Use semantic tokens only -- never introduce
bg-blue-500or similar Tailwind defaults into a modified component - ESLint exemptions apply --
src/components/ui/**is exempt frommax-linesandcomplexityrules since these are generated files (see ESLint Configuration)