Trovella Wiki

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

ComponentFileRadix PrimitiveDescription
Cardcard.tsxNoneSurface container with header, content, footer, and action slots
Dialogdialog.tsxDialogModal overlay with animated open/close transitions
Sheetsheet.tsxDialogSlide-out panel (used for mobile sidebar)
Collapsiblecollapsible.tsxCollapsibleExpandable/collapsible content section
ScrollAreascroll-area.tsxScrollAreaCustom-styled scrollbar container
Separatorseparator.tsxSeparatorHorizontal or vertical divider line
ComponentFileRadix PrimitiveDescription
Sidebarsidebar.tsxNoneFull sidebar system (provider, header, content, menu, groups)
Tabstabs.tsxTabsTabbed content with default and line variants
Breadcrumbbreadcrumb.tsxNoneBreadcrumb navigation trail
DropdownMenudropdown-menu.tsxDropdownMenuContext/action menu with keyboard navigation

Forms and Inputs

ComponentFileRadix PrimitiveDescription
Buttonbutton.tsxNonePrimary action element with 6 variants and 8 size options
Inputinput.tsxNoneText input field
Textareatextarea.tsxNoneMulti-line text input
Labellabel.tsxLabelForm field label
Selectselect.tsxSelectDropdown selection
Switchswitch.tsxSwitchToggle switch

Data Display

ComponentFileRadix PrimitiveDescription
Tabletable.tsxNoneData table with header, body, footer, and caption
Badgebadge.tsxNoneStatus indicator with 6 variants
Avataravatar.tsxAvatarUser avatar with image and fallback
Skeletonskeleton.tsxNoneLoading placeholder with pulse animation
Chartchart.tsxNoneRecharts wrapper with theme-aware chart colors

Feedback

ComponentFileRadix PrimitiveDescription
Alertalert.tsxNoneInline notification with icon, title, and description
Tooltiptooltip.tsxTooltipHover/focus information popup
Sonner (Toaster)sonner.tsxNoneToast 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:

  1. CSS targeting -- parent components can style children using has-data-[slot=...] selectors (used in Card headers to detect action slots)
  2. 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:

ComponentRole
SidebarProviderContext provider with cookie-persisted state and Cmd+B keyboard shortcut
SidebarMain container -- renders as a fixed panel on desktop, a Sheet on mobile
SidebarInsetMain content area adjacent to the sidebar
SidebarMenu / SidebarMenuItemNavigation list structure
SidebarMenuButtonNav item with active state, collapsed tooltip, and variant styling
SidebarGroup / SidebarGroupLabelGrouped 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:

  1. Review the generated code -- it becomes project-owned source
  2. Verify it uses @/lib/utils for cn() and @/components/ui/ for internal dependencies
  3. 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-slot attributes -- 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-500 or similar Tailwind defaults into a modified component
  • ESLint exemptions apply -- src/components/ui/** is exempt from max-lines and complexity rules since these are generated files (see ESLint Configuration)

On this page