Hey there, fellow developer! Installing shadcn/ui in your React project is a smart move — and you’ve come to the right place. Today I’ll show you how to set up a modern component library in 10–15 minutes that gives you not just beautiful interfaces, but full control over your code. No magic, no black boxes — just clean code that you can edit however you like.
Why shadcn/ui? It’s not an npm package you import. It’s a collection of ready-made, accessible components that get copied directly into your project. Want to change a button’s style? Edit the file — done. No override styles, no
!importanthacks.
Why Installing shadcn/ui in React Is the Right Choice for Modern Projects?
Before we dive into code, let’s be honest: why bother with setup when you have Material UI or Ant Design?
Here are three reasons that convinced me:
- Full code control — components live in
src/components/ui, you see every line. - Accessibility out of the box — ARIA attributes, keyboard navigation, focus management already configured.
- Perfect Tailwind CSS integration — style with utility classes, just like you’re used to.
Pro tip: If you’re just getting started with shadcn/ui, I recommend checking out the official documentation first to understand the project’s philosophy.
Step 1: Creating a Clean React Project with Vite
Let’s start from scratch. Open your terminal and run:
npm create vite@latest my-shadcn -- --template react
Stop the dev server with Ctrl+C if it launched automatically — we need to configure the environment first. After the project is created, navigate into it:
cd my-shadcn
Step 2: Installing Tailwind CSS v4
Shadcn/ui is built on Tailwind, so we can’t skip this. Install the dependencies:
npm install tailwindcss @tailwindcss/vite
Note: we’re using @tailwindcss/vite — this is the official plugin for Tailwind CSS v4 that works directly with Vite without PostCSS.
Step 3: Configuring vite.config.js
Open vite.config.js and update it to look like this:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { dirname, resolve } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
tailwindcss(),
],
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
server: {
open: true,
host: true,
strictPort: true,
fs: {
strict: false,
},
},
});
Why this matters:
- Alias
@— shadcn/ui uses it for imports. Without this setup, components won’t be found. server.open: true— the browser opens automatically afternpm run dev. Small thing, but nice.
Step 4: Adding jsconfig.json for Aliases
To make VS Code (and other editors) “understand” our @ alias, create a jsconfig.json file in the project root:
{
"compilerOptions": {
"ignoreDeprecations": "6.0",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
If you’re using TypeScript, create a
tsconfig.jsonwith a similarpathssection.
Step 5: Adding Tailwind Styles
Open src/index.css and completely replace its contents with a single line:
@import "tailwindcss";
In Tailwind v4, you no longer need @tailwind base/components/utilities — everything is imported with a single directive. Simple and clean.
Step 6: Adjusting eslint.config.js
When working with shadcn/ui, the rule react-refresh/only-export-components might complain. To avoid distractions from false warnings, let’s add an exception:
rules: {
'react-refresh/only-export-components': [
'off',
],
},
The final eslint.config.js should look something like this:
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
globals: globals.browser,
parserOptions: { ecmaFeatures: { jsx: true } },
},
rules: {
'react-refresh/only-export-components': [
'off',
],
},
},
])
Step 7: Initializing shadcn/ui
Everything’s ready for the main event! Run the initialization:
npx shadcn@latest init
Press Enter. After successful initialization, your project will have:
my-shadcn/
├── src/
│ ├── components/
│ │ └── ui/ # shadcn components will live here
│ ├── lib/
│ │ └── utils.js # helper utilities
│ └── index.css # our styles
└── components.json # shadcn config
Truth moment: if you see the
src/components/uifolder — congratulations, you’re halfway there!
Step 8: Adding the Components You Need
Shadcn/ui works on a “take only what you need” principle. Let’s add a set for a demo dashboard:
npx shadcn@latest add card avatar badge dialog dropdown-menu sonner
After running the command, files like button.jsx, card.jsx, avatar.jsx and others will appear in src/components/ui. Each one is a ready-made, accessible component that you can edit however you want.
Step 9: Writing the Demo App in App.jsx
Let’s see the result right away! Open src/App.jsx and replace all its contents with this code:
import { Button } from "./components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./components/ui/card"
import { Avatar, AvatarFallback, AvatarImage } from "./components/ui/avatar"
import { Badge } from "./components/ui/badge"
import { Bell, Heart, Star, User } from "lucide-react"
function App() {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-100 to-gray-200 p-8">
<div className="max-w-6xl mx-auto">
{/* Header */}
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold text-gray-900">My Dashboard</h1>
<div className="flex gap-2">
<Button variant="outline" size="icon">
<Bell className="h-4 w-4" />
</Button>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</div>
</div>
{/* Cards grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Subscribers</CardTitle>
<User className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+12,234</div>
<p className="text-xs text-muted-foreground">+20% from last month</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Likes</CardTitle>
<Heart className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+573</div>
<p className="text-xs text-muted-foreground">+201 since last week</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Rating</CardTitle>
<Star className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">4.8</div>
<p className="text-xs text-muted-foreground">out of 5 stars</p>
</CardContent>
</Card>
</div>
{/* Main card with actions */}
<Card>
<CardHeader>
<CardTitle>Hey, developer! </CardTitle>
<CardDescription>
Here's what you can do with shadcn/ui
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex gap-2 flex-wrap">
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
</div>
<div className="flex gap-4">
<Button>Regular Button</Button>
<Button variant="outline">Outline Button</Button>
<Button variant="ghost">Ghost Button</Button>
<Button variant="destructive">Delete</Button>
</div>
<div className="flex gap-4">
<Button size="sm">Small</Button>
<Button size="default">Default</Button>
<Button size="lg">Large</Button>
<Button size="icon">
<Star className="h-4 w-4" />
</Button>
</div>
</CardContent>
<CardFooter className="border-t pt-6">
<p className="text-sm text-muted-foreground">
shadcn/ui gives you full control over components — change whatever you want!
</p>
</CardFooter>
</Card>
</div>
</div>
)
}
export default App
Don’t forget: you can safely delete the
src/App.cssfile — we’re using only Tailwind classes.
Step 10: Launch and Enjoy!
Final touch:
npm run dev
The browser will open automatically, and you’ll see a beautiful dashboard with cards, buttons, badges, and avatars. Everything works, everything is responsive, everything is accessible.
Checklist: Did You Do Everything Right?
Go through these points if something didn’t work:
- In
vite.config.js, thetailwindcss()plugin and@alias are added - In
jsconfig.json, paths for the alias are configured - In
src/index.css, there’s only@import "tailwindcss"; - In
eslint.config.js, thereact-refresh/only-export-componentsrule is disabled - After
npx shadcn@latest init, thesrc/components/uifolder appeared - Components were added with
npx shadcn@latest add ...
Not working? Most often, the issue is with the
@alias. Make surejsconfig.jsonis in the project root, not insidesrc/.
Conclusion: Why Installing shadcn/ui Was Worth It
When I first tried shadcn/ui, one thought stuck with me: “What if components aren’t a black box, but just code I can read and change?”.
That’s exactly what shadcn/ui delivers: beautiful, accessible interfaces without compromises. You’re not tied to a package version, not waiting for fixes from maintainers, not fighting styles with !important. You just write code — and it works.
Useful Links
- Official shadcn/ui Website
- Installation Documentation
- Component Collection
- Theming Customization Guide
- Lucide React Icons (used in the example)