Introduction

As a React developer, you may be tired of writing custom components from scratch or dealing with rigid UI libraries like Material UI or Ant Design. Meet ShadCN UI: a sleek, minimal, and customizable component library built on Tailwind CSS and Radix UI. Whether you're building a dashboard, an e-commerce platform, or a SaaS application, ShadCN UI provides elegant, accessible, and developer-friendly components to speed up your workflow.

In this blog, we’ll explore why ShadCN UI stands out, how to integrate it, and how to make the most of its features. Let’s dive in! 🚀

About ShadCN

If you've ever struggled with UI libraries that lack flexibility or require complex overrides, ShadCN UI offers a refreshing alternative, providing flexibility and ease of customization.

ShadCN UI is not a typical component library—it's more like a collection of pre-built, customizable components that live inside your project, giving you complete control. It is built on Tailwind CSS for styling and Radix UI for accessibility and interactions, ensuring a smooth developer experience.

Unlike traditional UI libraries that impose strict design constraints, ShadCN UI is unopinionated, allowing you to style and extend components freely.

Setting up ShadCN with NextJS project

First, let's setup a NextJS project

npx create-next-app@latest

On installation, you'll see the following prompts:

What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like your code inside a `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to use Turbopack for `next dev`?  No / Yes
Would you like to customize the import alias (`@/*` by default)? No / Yes
What import alias would you like configured? @/*

After the prompts, create-next-app will create a folder with your project name and install the required dependencies.

Now, let's setup ShadCN UI by running this command:

# using npm or yarn
npx shadcn-ui@latest init

# using pnpm 
pnpm dlx shadcn@latest init

# using bun
bunx --bun shadcn@latest init

This command will set up the necessary dependencies and configurations.
Now, you can add individual components in your project.

# using npm or yarn
npx shadcn@latest add button

# using pnpm
pnpm dlx shadcn@latest add button

# using bun 
bunx --bun shadcn@latest add button

The command above will add the Button component to your project. You can then import it like this:

import { Button } from "@/components/ui/button"

export default function Home() {
  return (
    <div>
      <Button>Click me</Button>
    </div>
  )
}

Customizing ShadCN Components

Since ShadCN UI is a file based library, all components are stored in your project and you can freely customize them.

Customize the Button Component

Navigate to components/ui/button.tsx and update the styles:

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-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
  {
    variants: {
      variant: {
        default:
          "bg-primary text-primary-foreground shadow hover:bg-primary/90",
        destructive:
          "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
        outline:
          "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
        secondary:
          "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2",
        sm: "h-8 rounded-md px-3 text-xs",
        lg: "h-10 rounded-md px-8",
        icon: "h-9 w-9",
      },
    },
    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 }

Now you have full control over the component’s styling and behavior!

Building a Simple UI with ShadCN

Let's build a login form using ShadCN components

"use client";

import React from "react";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";

// define signin form schema using zod
const formSchema = z.object({
  email: z.string().email({
    message: "Invalid email address!",
  }),
  password: z.string().min(2, {
    message: "Password required!",
  }),
});

const SignIn: React.FC<SignInProps> = ({}) => {

  // Define your form
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      email: "",
      password: "",
    },
  });

// Define a submit handler
 function onSubmit(values: z.infer<typeof formSchema>) {
    console.log(values)
  }

  return (
    <div className="flex items-center justify-center w-full h-screen bg-primary bg-opacity-5">
      <Card className="w-[35%] sm:p-4 p-3 rounded-lg shadow-xl">
        <CardHeader className="mb-6">
          <CardTitle className="text-3xl font-bold text-center">
            Sign In
          </CardTitle>
        </CardHeader>
        <CardContent>
          <Form {...form}>
            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
              <FormField
                control={form.control}
                name="email"
                render={({ field }) => (
                  <FormItem className="w-full">
                    <FormLabel className="text-lg font-medium">Email</FormLabel>
                    <FormControl>
                      <Input
                        type="email"
                        placeholder="Email"
                        className="border-gray-400 py-2 focus-visible:ring-primary text-lg"
                        {...field}
                      />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
              <FormField
                control={form.control}
                name="password"
                render={({ field }) => (
                  <FormItem className="w-full">
                    <FormLabel className="text-lg font-medium">
                      Password
                    </FormLabel>
                    <FormControl>
                      <Input
                        type="password"
                        placeholder="Password"
                        className="border-gray-400 py-2 focus-visible:ring-primary text-lg"
                        {...field}
                      />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
              <div className="w-full flex justify-center">
                <Button
                  type="submit"
                  className="bg-primary text-white py-2 px-3 mt-2 font-medium text-lg w-full"
                >
                  Sign In
                </Button>
              </div>
            </form>
          </Form>
        </CardContent>
      </Card>
    </div>
  );
};

export default SignIn;

interface SignInProps {}

This form is lightweight, fully customizable, and follows accessibility best practices!

Conclusion

ShadCN UI is a game-changer for developers who love Tailwind CSS and customizability. It provides a flexible alternative to bulky UI libraries while ensuring accessibility and modern design. If you're building a React app, give ShadCN UI a try—it might become your go-to component library!

🚀 Ready to explore more?
Check out the official ShadCN UI docs and start building stunning UIs today!
Happy coding! 🎉