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:
{
"$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
The Button component is already included in the project:
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 } />
}
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:
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:
// 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