shadcn/ui
Pre-built components you own completely. Not a library you install — code you copy into your project and control forever.
Every web application needs buttons, forms, dialogs, dropdowns, and data tables. These are not unique to your product — they are the vocabulary of every interface. Building them from scratch each time is wasteful. Using a rigid third-party library creates a different problem: you cannot change what you do not own.
shadcn/ui solves this with an unusual philosophy.
The Philosophy: You Own the Code
Most UI libraries work like this: you install a package, import components from it, and use them. The component code lives in node_modules — a folder you do not touch and cannot meaningfully modify.
When the library has a bug, you wait for a fix. When the library breaks in a new version, your app breaks too. When you need a small change to how the button works, you fight the library's override system.
shadcn/ui works differently.
When you add a shadcn/ui component, the source code is copied directly into your project. It becomes your code. You can read it, modify it, delete parts of it, and extend it however you need. There is no dependency to update, no black box to fight.
After running the npx shadcn@latest add button command, you have real, readable source files in your codebase.
The analogy: Imagine Excel add-ins that you could not modify. You can use them as-is, but if you need a formula to work slightly differently, you are stuck. Now imagine instead that installing an add-in gave you the VBA source code — you can use it as-is, or modify it however you want. That is the difference between a traditional UI library and shadcn/ui.
The Real Button Component
Here is the actual Button component from Udyogaseva's codebase — copied from shadcn/ui and then customized for the platform's design:
Notice the hero and hero-outline variants — those were added after the component was copied into the project. You cannot do that with a library. You can do it when you own the code.
In use:
The Real Card Component
The Card component from Udyogaseva — used on every page of the admin dashboard:
Using it:
The cn() utility function merges class names — it lets you add extra classes without overwriting the component's defaults. <Card className="border-blue-200"> still gets the default rounded-lg border bg-card shadow-sm plus the custom border-blue-200.
The Badge Component
Used throughout Udyogaseva for status labels — active/suspended, verified/pending, open/closed:
The Skeleton Component — Never Use Spinners for Content
This is a specific rule worth understanding deeply.
When data is loading, there are two approaches:
- Show a spinner in the center of the screen while waiting
- Show placeholder shapes where the content will appear (skeleton loading)
Spinners are fine for short operations (button submits, form saves). For content loading — a list of candidates, a dashboard with stats, a table — skeletons are always better.
Why: Skeletons tell the user exactly what is coming and where it will be. The page structure is visible before the data arrives. The user can start reading the layout. When the data arrives, it fills in. No jarring "the page just completely changed" moment.
The Skeleton component from Udyogaseva is five lines — an animated pulsing grey rectangle. The size is controlled by your className.
When isLoading is true, a skeleton appears. When data arrives, the number appears in the same space. No layout shift. No spinner. Professional.
Skeleton component in production — every stat card and table row has one.Key Components to Know
These are the shadcn/ui components you will use on almost every project:
| Component | When to use |
|---|---|
Button | Every clickable action |
Card | Contained sections of content |
Input | Text fields in forms |
Select | Dropdown selection |
Dialog | Modal popups — confirmations, forms |
DropdownMenu | Context menus, action menus |
Tabs | Multiple views of the same section |
Badge | Status labels, category tags |
Skeleton | Loading states for any content |
Toast | Brief notification messages |
Table | Structured data display |
Form | Form validation integration with React Hook Form |
When to Use shadcn vs Build from Scratch
Use shadcn/ui for:
- Any element that appears in the shadcn component list
- Standard interactions: buttons, forms, dialogs, dropdowns, tabs
- Anything where accessibility matters (keyboard navigation, screen readers) — shadcn handles this correctly by default
Build from scratch when:
- The UI is unique to your product (a custom chart, a specific visualization, a branded hero section)
- shadcn's component does not match the interaction you need at all
- You are building something simple enough that the full shadcn component is overkill
The test: If you could describe what you need in one word ("a button", "a modal", "a dropdown"), use shadcn. If the description takes a sentence, consider whether shadcn's version fits or if you need something custom.
ui.shadcn.com/components. Scan the component list. Identify five components you would use in a CA firm's client portal: a dialog for confirming file deletion, a table for client list, a badge for engagement status, a form for new client intake, and a toast for upload confirmation.The cn() Utility Function
Every shadcn component uses a utility function called cn. It merges Tailwind classes intelligently:
You will see cn() everywhere in the Udyogaseva codebase. It is part of the standard pattern — always use it when combining conditional classes.
A Complete Example: Candidate Status Card
Here is a real pattern combining Card, Badge, Skeleton, and Button — the kind of component that appears in the Udyogaseva admin candidate list:
This is the pattern. Components composed from smaller components. Loading state handled. Status communicated visually. Consistent spacing and typography. Every visual decision derived from the design system.
In the next lesson, we put all of this together to build real page layouts.