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 !important hacks.

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:

  1. Full code control — components live in src/components/ui, you see every line.
  2. Accessibility out of the box — ARIA attributes, keyboard navigation, focus management already configured.
  3. 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 after npm 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.json with a similar paths section.

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/ui folder — 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.css file — 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, the tailwindcss() 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, the react-refresh/only-export-components rule is disabled
  • After npx shadcn@latest init, the src/components/ui folder appeared
  • Components were added with npx shadcn@latest add ...

Not working? Most often, the issue is with the @ alias. Make sure jsconfig.json is in the project root, not inside src/.

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

Read also