Install shadcn/ui Manually
Install shadcn/ui manually in any React project with custom setup for webpack, parcel, and other build tools. Complete TypeScript configuration guide.
Need help with manual installation?
Join our Discord community for help from other developers.
How to install shadcn/ui manually
Sometimes the CLI doesn't cover your setup. Maybe you're using a custom build tool, integrating with an existing design system, or just prefer doing things by hand. Here's exactly what shadcn/ui needs to work.
Core requirements
Before starting, make sure you have:
- React 18 or higher
- Tailwind CSS installed and working
- A build tool that handles TypeScript/JSX
Step-by-step setup
Install dependencies
pnpm add class-variance-authority clsx tailwind-merge lucide-react tw-animate-css
What each package does:
- class-variance-authority - Type-safe component variants
- clsx - Conditional classNames
- tailwind-merge - Merge Tailwind classes without conflicts
- lucide-react - Icon library used by components
- tw-animate-css - Additional animation utilities
Configure path aliases
Add to your tsconfig.json
:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Adjust the path to match your project structure. This enables clean imports.
Also configure your bundler to understand these paths:
Webpack:
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
Rollup/Vite:
resolve: {
alias: {
'@': './src'
}
}
Add CSS variables and Tailwind config
Create or update your global CSS file:
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--radius: 0.625rem;
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-border: var(--border);
--color-ring: var(--ring);
/* Map all other variables */
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
Create the cn utility
Create lib/utils.ts
:
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
This utility merges classes intelligently, handling Tailwind conflicts.
Create components.json
This file tells the CLI about your project structure:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
Adjust paths to match your project.
Add your first component
Now you can either:
Use the CLI:
npx shadcn@latest add button
Or copy manually:
Create 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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
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 hover:text-accent-foreground",
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-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
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 };
Verifying your setup
Test that everything works:
// App.tsx
import { Button } from "@/components/ui/button";
function App() {
return (
<div className="p-8">
<Button>It works!</Button>
<Button variant="outline">Outline</Button>
<Button variant="destructive">Destructive</Button>
</div>
);
}
If the buttons render with proper styling, you're good to go.
Framework-specific notes
Create React App
CRA doesn't support path aliases without ejecting. Use relative imports or craco
:
// craco.config.js
module.exports = {
webpack: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
};
Parcel
Parcel needs an .parcelrc
:
{
"extends": "@parcel/config-default",
"resolvers": ["@parcel/resolver-default"],
"transformers": {
"*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
}
}
Webpack 5
Add to your webpack config:
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
},
extensions: ['.ts', '.tsx', '.js', '.jsx']
}
Customization options
Custom color palette
Replace the CSS variables with your brand colors:
:root {
--primary: oklch(0.5 0.3 250); /* Your brand blue */
--secondary: oklch(0.7 0.2 120); /* Your brand green */
}
Add a CSS prefix
To avoid conflicts, add a prefix in components.json
:
{
"tailwind": {
"prefix": "ui-"
}
}
Now classes become ui-bg-primary
instead of bg-primary
.
Different icon library
Don't want Lucide? Install your preferred library and update imports:
// Using Heroicons instead
import { CheckIcon } from "@heroicons/react/24/outline";
Troubleshooting
Classes not applying
Check that:
- Tailwind CSS is processing your component files
- CSS variables are defined in your global CSS
- The
cn()
utility is imported correctly
TypeScript errors
Make sure your tsconfig.json
includes:
{
"compilerOptions": {
"jsx": "react-jsx",
"esModuleInterop": true,
"skipLibCheck": true
}
}
Build errors
Common fixes:
- Ensure all peer dependencies are installed
- Check that your bundler handles
.tsx
files - Verify path aliases are configured in both TypeScript and your bundler
What's next
With manual setup complete:
- Browse official components for forms, tables, and UI elements
- Add charts for data visualization
- Explore community components for extended functionality
- Add useful hooks to enhance your components
- Use pre-built blocks to quickly build common layouts
The manual approach gives you complete control over every aspect of the setup. Perfect for when you need shadcn/ui to fit into your existing architecture exactly how you want it.
Questions you're probably thinking
Install shadcn/ui Laravel
Install shadcn/ui with Laravel and Inertia.js for React components in PHP applications. Complete setup guide for TypeScript integration.
Install shadcn/ui Next.js
Install shadcn/ui in Next.js 14 and 15 projects with App Router and Server Components. Complete setup guide for TypeScript and Tailwind CSS.