Skip to main content

Component Library

The application uses shadcn/ui - a collection of re-usable components built with Radix UI and Tailwind CSS. Unlike traditional component libraries, shadcn/ui components are copied into your project, giving you full control to customize them.
shadcn/ui components are not installed as dependencies. They’re copied into components/ui/ and become part of your codebase.

shadcn/ui Configuration

The project is configured in components.json:
components.json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "radix-maia",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "iconLibrary": "hugeicons",
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  }
}

Key Configuration Options

  • Style: radix-maia - Modern component style variant
  • RSC: true - React Server Components support
  • Icon Library: hugeicons - @hugeicons/react for icons
  • CSS Variables: true - Theme colors via CSS variables

Adding Components

Add new shadcn/ui components using the CLI:
bunx shadcn@latest add button

Available Components

Common components you can add:
  • button - Button component with variants
  • card - Card container component
  • dialog - Modal dialog component
  • dropdown-menu - Dropdown menu component
  • form - Form components with validation
  • input - Input field component
  • select - Select dropdown component
  • table - Data table component
  • tabs - Tabbed interface component
  • toast - Toast notification component
See the full list at shadcn/ui components

Core Components

Button Component

The Button component is already included in the project:
components/ui/button.tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"

const buttonVariants = cva(
  "group/button inline-flex shrink-0 items-center justify-center rounded-4xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 active:translate-y-px disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/80",
        outline: "border-border bg-input/30 hover:bg-input/50 hover:text-foreground",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-muted hover:text-foreground",
        destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 gap-1.5 px-3",
        xs: "h-6 gap-1 px-2.5 text-xs",
        sm: "h-8 gap-1 px-3",
        lg: "h-10 gap-1.5 px-4",
        icon: "size-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

export function Button({ className, variant, size, asChild = false, ...props }) {
  const Comp = asChild ? Slot.Root : "button"
  return <Comp className={cn(buttonVariants({ variant, size, className }))} {...props} />
}

Button Usage

import { Button } from "@/components/ui/button"

// Default button with primary color
<Button>Click me</Button>

// Different variants
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Delete</Button>
<Button variant="link">Link</Button>

// Different sizes
<Button size="xs">Extra Small</Button>
<Button size="sm">Small</Button>
<Button size="default">Default</Button>
<Button size="lg">Large</Button>
<Button size="icon"><Icon /></Button>

ThemeProvider Component

The ThemeProvider manages dark/light mode:
components/theme-provider.tsx
"use client"

import * as React from "react"
import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes"

export function ThemeProvider({ children, ...props }) {
  return (
    <NextThemesProvider
      attribute="class"
      defaultTheme="system"
      enableSystem
      disableTransitionOnChange
      {...props}
    >
      <ThemeHotkey />
      {children}
    </NextThemesProvider>
  )
}

Theme Features

  • System theme detection - Respects OS preference
  • Keyboard shortcut - Press d to toggle dark mode
  • No transition flash - Prevents theme flash on page load
  • Class-based switching - Uses .dark class for dark mode

Using Theme in Components

"use client"

import { useTheme } from "next-themes"

export function ThemeToggle() {
  const { theme, setTheme } = useTheme()

  return (
    <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
      Toggle {theme === "dark" ? "Light" : "Dark"} Mode
    </button>
  )
}

Utility Functions

cn() Helper

The cn() utility combines Tailwind classes with class-variance-authority:
lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Usage Example

import { cn } from "@/lib/utils"

// Merge classes conditionally
const buttonClass = cn(
  "px-4 py-2 rounded",
  isActive && "bg-primary text-white",
  isDisabled && "opacity-50 cursor-not-allowed"
)

// Override component styles
<Button className={cn("custom-styles", props.className)} />

Icon Library

The project uses @hugeicons/react for iconography:
import { Home01Icon, Settings01Icon, User01Icon } from "@hugeicons/react"

<Button>
  <Home01Icon className="size-4" />
  Home
</Button>

Icon Features

  • 4000+ icons available
  • Consistent sizing with Tailwind classes
  • Fully customizable with className prop
  • Tree-shakeable - Only imported icons are bundled

Component Best Practices

Follow the single responsibility principle. Each component should do one thing well.
// Good: Focused component
function UserAvatar({ src, name }) {
  return <img src={src} alt={name} className="rounded-full" />
}

// Bad: Component doing too much
function UserProfile() {
  // Fetching data, rendering UI, handling state, etc.
}
Define clear prop types for better type safety and developer experience:
interface ButtonProps {
  variant?: "default" | "outline" | "ghost"
  size?: "sm" | "md" | "lg"
  children: React.ReactNode
  onClick?: () => void
}

export function Button({ variant = "default", size = "md", children, onClick }: ButtonProps) {
  // Component implementation
}
Use Server Components by default for better performance:
// Server Component (default)
export function UserList() {
  // Can fetch data directly, no API route needed
  return <div>User list</div>
}

// Client Component (when needed)
"use client"
export function InteractiveButton() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
Build complex UIs by composing simple components:
function Card({ children }) {
  return <div className="rounded-lg border p-4">{children}</div>
}

function CardHeader({ children }) {
  return <div className="mb-4 font-semibold">{children}</div>
}

function CardContent({ children }) {
  return <div>{children}</div>
}

// Usage
<Card>
  <CardHeader>Title</CardHeader>
  <CardContent>Content here</CardContent>
</Card>

Customizing Components

Since shadcn/ui components are part of your codebase, you can customize them freely:

Example: Customizing Button

components/ui/button.tsx
// Add a new variant
const buttonVariants = cva(
  "base-classes",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground",
        // Add your custom variant
        success: "bg-green-600 text-white hover:bg-green-700",
      },
    },
  }
)

Example: Adding Animation

import { motion } from "framer-motion"

export function AnimatedButton(props) {
  return (
    <motion.div
      whileHover={{ scale: 1.05 }}
      whileTap={{ scale: 0.95 }}
    >
      <Button {...props} />
    </motion.div>
  )
}

Next Steps

Styling Guide

Learn about Tailwind CSS and theming

Browse Components

Explore all available shadcn/ui components

Build docs developers (and LLMs) love