When building modern front-end applications, developers often struggle to strike the perfect balance between accessibility, design flexibility, and developer experience. Shadcn UI offers a unique solution — combining the power of Radix UI primitives with Tailwind CSS in a developer-friendly and fully customizable package.
In this post, we’ll explore why Shadcn UI is gaining traction and walk through a step-by-step guide to set it up in a React + TypeScript project. We’ll also build a simple yet styled and accessible Button component using the library.
Why Use Shadcn UI?
Below are some compelling reasons why developers are turning to Shadcn UI:
1. Built with Radix UI Primitives
Shadcn UI leverages Radix UI for accessibility and behavior, ensuring components are built with accessibility best practices out of the box.
2. Full Control of Styling
Unlike traditional UI libraries, Shadcn UI doesn’t abstract styling away. You get full control with Tailwind CSS and can easily theme or extend components as needed.
3. Copy-Paste Component Model
Instead of a bloated npm package, Shadcn gives you the flexibility to copy only the components you need into your codebase — no unnecessary bundle size penalties.
4. TypeScript-First
Components are built with TypeScript, providing excellent type safety and IDE support.
5. Easily Maintainable and Customizable
Since the components live in your codebase, you can modify, optimize, and maintain them like any of your own custom components.
Setting Up Shadcn UI in a React + TypeScript Project
Let’s walk through integrating Shadcn UI into a new Vite + React + TypeScript project and using the Button component.
Step 1: Create a New Vite + React + TypeScript App
npm create vite@latest my-shadcn-app -- --template react-ts
cd my-shadcn-app
npm install
Step 2: Install Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Edit your tailwind.config.ts
:
import type { Config } from "tailwindcss";
const config: Config = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
export default config;
In src/index.css
, add:
@tailwind base;
@tailwind components;
@tailwind utilities;
Step 3: Install and Initialize Shadcn UI
npm install shadcn@latest
npx shadcn@latest init
During the prompt, you will be asked:
- Which color would you like to use as the base color? — choose desired color
This generates a components
folder with a utility-based setup.
Step 4: Add the Button Component
To install a Button component:
npx shadcn@latest add button
This will create a reusable Button.tsx component inside your components/ui folder.
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 gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
Step 5: Use the Button in Your App
Update App.tsx
:
import { Button } from "@/components/ui/button";
function App() {
return (
<div className="flex h-screen items-center justify-center bg-gray-100">
<Button variant="default" onClick={() => alert("Shadcn Button Clicked")}>
Click Me
</Button>
</div>
);
}
export default App;
The variant="default" prop controls the button style, and you can customize or create your own variants by editing the component’s classNames.
Final Thoughts
Shadcn UI Library brings a new approach to component libraries: opinionated yet fully customizable, accessible yet unrestrictive. It gives developers control without compromising on design consistency or accessibility standards.
Whether you’re building an internal dashboard or a polished product UI, Shadcn UI is worth exploring — especially if you’re already using Tailwind CSS and want an ergonomic and maintainable component structure.
If you’re tired of bloated UI frameworks and want to own your UI code, Shadcn UI Library may be exactly what you need.
Top comments (0)