shadcn/ui - Customization
Theming with CSS Variables
shadcn/ui uses CSS variables for theming. Customize in globals.css:
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
/* ... other dark mode variables */
}
}Adding Custom Variants to Button
Since you own the code, extend components directly:
// components/ui/button.tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent",
// Add custom variant
custom: "bg-gradient-to-r from-purple-500 to-pink-500 text-white",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
// Add custom size
xl: "h-14 rounded-md px-10 text-lg",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }Custom Color Themes
To use a custom brand color as primary:
@layer base {
:root {
/* Custom brand color (blue) */
--primary: 217 91% 60%;
--primary-foreground: 0 0% 100%;
/* Custom accent (purple) */
--accent: 270 67% 47%;
--accent-foreground: 0 0% 100%;
}
}cn() Utility Function
The cn() utility combines clsx and tailwind-merge for conditional class names:
// lib/utils.ts
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// Usage examples
cn("px-4 py-2", isActive && "bg-primary text-white")
cn("text-sm", size === "lg" && "text-lg", className)Extending a Component
Create wrapper components to add functionality without modifying the base:
// components/ui/loading-button.tsx
"use client"
import { Button, ButtonProps } from "@/components/ui/button"
import { Loader2 } from "lucide-react"
interface LoadingButtonProps extends ButtonProps {
loading?: boolean
}
export function LoadingButton({ loading, children, disabled, ...props }: LoadingButtonProps) {
return (
<Button disabled={loading || disabled} {...props}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{children}
</Button>
)
}
// Usage
<LoadingButton loading={isSubmitting} type="submit">
Save Changes
</LoadingButton>components.json Configuration
The components.json file controls CLI behavior:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}Security: Only use trusted registry URLs in
components.json. Never point to untrusted third-party registry endpoints.