Leave Localhost logoLeave LocalhostDocs
UI

shadcn/ui

Leave Localhost uses shadcn/ui as its component foundation. Components live in the shared packages/ui package and are configured with the new-york style, Lucide icons, and CSS variables for theming.

Leave Localhost uses shadcn/ui as its component foundation. Components live in the shared packages/ui package and are configured with the new-york style, Lucide icons, and CSS variables for theming.

Configuration

The shadcn configuration file is at packages/ui/components.json:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": true,
  "tsx": true,
  "iconLibrary": "lucide",
  "tailwind": {
    "config": "",
    "css": "src/globals.css",
    "baseColor": "gray",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "hooks": "@/hooks",
    "lib": "@/lib",
    "ui": "@/components",
    "utils": "@/utils"
  }
}

Key decisions:

  • style: "new-york" — the more refined shadcn variant with smaller elements and tighter spacing.
  • rsc: true — components are React Server Component safe by default. Those that require client state add "use client" explicitly.
  • cssVariables: true — all colors resolve through CSS custom properties, enabling runtime theming and dark mode.

Adding a New Component

Run the shadcn CLI from the UI package root:

bunx --bun shadcn@latest add <component> --cwd packages/ui

The CLI reads components.json, writes the component file to src/components/, and installs any missing Radix dependencies.

After adding a component:

  1. Export it from packages/ui/package.json under "exports":

    "./accordion": "./src/components/accordion.tsx"
  2. Import it in your app by the new entry point:

    import { Accordion } from "@leavelocalhost/ui/accordion";
  3. Run typechecks to verify the new export resolves:

    bun run typecheck

Updating An Existing Component

Refresh one component at a time so generated diffs stay reviewable:

bunx --bun shadcn@latest add <component> --overwrite --cwd packages/ui

Then inspect the diff before keeping it:

git diff -- packages/ui/src/components/<component>.tsx packages/ui/package.json

Check generated imports. Components should import local helpers through the UI package layout, usually ../utils, not app-local aliases from apps/app or apps/marketing.

If the refreshed component adds a new dependency, keep it in packages/ui/package.json. If it adds a new shared component file, add an entry to the exports map before importing it from an app.

After Adding Components

Run the focused verification first:

bun --cwd packages/ui lint
bun --cwd packages/ui typecheck

Then run the affected app checks:

bun run lint
bun run typecheck
bun --cwd apps/app build
bun --cwd apps/marketing build

Use the app build that matches the component's consumers if only one app imports the new shared component.

Customizing Existing Components

shadcn components are copied into the project, not installed as a dependency. This means you own the source code and can modify it freely.

Common customization patterns:

  • Add a variant — extend the cva() call. For example, the Button component defines variants for default, destructive, outline, secondary, ghost, and link. Add a new variant key and its Tailwind classes.
  • Change sizes — modify the size variants in the cva() definition. The Button ships with default, xs, sm, lg, icon, icon-xs, icon-sm, and icon-lg.
  • Adjust styling — edit the Tailwind classes directly in the component file. All components use the cn() utility to merge default and consumer-provided classes, so overrides from the call site work naturally.

Component Architecture

shadcn components in this project follow a consistent pattern:

import { cn } from "../utils";

function Card({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card"
      className={cn("rounded-xl border bg-card p-6", className)}
      {...props}
    />
  );
}
  • data-slot attributes identify component parts for debugging and styling.
  • cn() merges default styles with consumer overrides.
  • React.ComponentProps<> provides full HTML attribute forwarding.
  • asChild via @radix-ui/react-slot lets you swap the rendered element (e.g. rendering a Button as a Next.js Link).

Variant-Driven Components

Components with multiple visual states use class-variance-authority:

import { cva, type VariantProps } from "class-variance-authority";

const buttonVariants = cva("base-classes", {
  variants: {
    variant: {
      default: "bg-primary text-primary-foreground",
      destructive: "bg-destructive text-destructive-foreground",
    },
    size: {
      default: "h-9 px-4",
      sm: "h-8 px-3",
    },
  },
  defaultVariants: {
    variant: "default",
    size: "default",
  },
});

This approach makes variants type-safe and composable. Import buttonVariants if you need to apply the same styles outside of the Button component.

The Sidebar System

The Sidebar is the most complex component in the package. It provides a complete dashboard navigation system with:

  • SidebarProvider — context for open/collapsed state, persisted via a cookie (sidebar_state).
  • Sidebar — the main container with variant (sidebar, floating, inset), side (left, right), and collapsible (offcanvas, icon, none) props.
  • SidebarMenuButton — navigation items with isActive state, tooltip support when collapsed, and asChild for routing integration.
  • SidebarTrigger — toggle button with ⌘B keyboard shortcut.
  • Mobile — on viewports below 768 px, the sidebar renders as a Sheet (slide-over drawer).

See Customizing the Dashboard for guidance on modifying the sidebar layout.

Tips

  • Don't import from src/ — always use the package entry points (@leavelocalhost/ui/button, etc.). The exports map in package.json is the public API.
  • Keep shared vs. app-specific separate — if a component is only used in apps/app, put it in apps/app/src/components. Move it to packages/ui only when both apps need it.
  • Check cn() mergingtailwind-merge resolves class conflicts intelligently (e.g., bg-red-500 overrides bg-blue-500). Use cn() for any conditional class composition.

On this page