Module 23 — shadcn/ui in Next.js
shadcn/ui is a collection of reusable, accessible UI components designed to work well with React, Next.js, Tailwind CSS, and modern application architectures.
Unlike a traditional component library where you install a package and import components from a hidden implementation, shadcn/ui typically adds the component source code directly into your project.
This gives you much more control over:
- Component styling
- Component behavior
- Accessibility
- Variants
- Design tokens
- Customization
- Application design system
A useful mental model is:
1Tailwind CSS 2 ↓ 3Styling System 4 5shadcn/ui 6 ↓ 7Reusable UI Components 8 9Next.js 10 ↓ 11Application Architecture
Together they provide a strong foundation for building production interfaces.
What Is shadcn/ui?
A traditional UI library might work like:
1import { Button } from "some-ui-library";
The implementation lives inside the installed library.
With shadcn/ui, the component source is added to your application.
Conceptually:
1Your Next.js Project 2│ 3├── components/ 4│ └── ui/ 5│ ├── button.tsx 6│ ├── card.tsx 7│ ├── dialog.tsx 8│ └── input.tsx 9│ 10└── app/
You own and can modify the component code.
This makes shadcn/ui particularly useful for applications that need a customized design system.
Why Use shadcn/ui?
Building every UI component from scratch can become repetitive.
Without a component system:
1Page 1 2 └── Custom Button 3 4Page 2 5 └── Another Button 6 7Page 3 8 └── Different Button 9 10Page 4 11 └── Another Dialog
Eventually, the application becomes visually inconsistent.
With reusable components:
1Button 2Card 3Dialog 4Input 5Table 6Tabs 7Dropdown
the application can maintain consistent behavior and appearance.
shadcn/ui + Tailwind CSS
shadcn/ui works particularly well with Tailwind CSS.
Think of the relationship as:
1Tailwind CSS 2 ↓ 3Low-level styling utilities 4 ↓ 5shadcn/ui 6 ↓ 7Reusable components 8 ↓ 9Application UI
For example:
1<Button> 2 Enroll Now 3</Button>
instead of repeatedly writing a long collection of classes.
Installation
Start with a Next.js application that already has Tailwind CSS configured.
Then initialize shadcn/ui using its CLI according to the current installation instructions.
A typical workflow is:
1npx shadcn@latest init
The CLI configures the project and prepares the component structure.
You can then add individual components when needed.
For example:
1npx shadcn@latest add button
Then:
1npx shadcn@latest add card
And:
1npx shadcn@latest add dialog
The exact CLI options can evolve, so use the current shadcn/ui documentation when starting a new project.
Component Architecture
A common structure is:
1components/ 2├── ui/ 3│ ├── button.tsx 4│ ├── card.tsx 5│ ├── dialog.tsx 6│ ├── input.tsx 7│ ├── select.tsx 8│ └── table.tsx 9│ 10├── navbar.tsx 11├── sidebar.tsx 12├── course-card.tsx 13└── course-dashboard.tsx
The distinction is important.
UI Components
1components/ui/
contain reusable building blocks.
For example:
1Button 2Card 3Input 4Dialog 5Table
Application Components
1components/
contain components specific to your application.
For example:
1CourseCard 2CourseSidebar 3DashboardHeader 4LessonNavigation
This gives you:
1Primitive UI 2 ↓ 3Application Components 4 ↓ 5Pages
Button
The Button component is one of the most commonly used components.
Example:
1import { Button } from "@/components/ui/button"; 2 3export default function CourseActions() { 4 return ( 5 <div className="flex gap-3"> 6 <Button> 7 Start Learning 8 </Button> 9 10 <Button variant="outline"> 11 View Syllabus 12 </Button> 13 </div> 14 ); 15}
The component can provide different variants:
1default 2outline 3secondary 4ghost 5destructive
The exact available variants depend on your generated component configuration.
Button Variants
For example:
1<Button> 2 Enroll 3</Button> 4 5<Button variant="outline"> 6 Preview 7</Button> 8 9<Button variant="secondary"> 10 Save 11</Button> 12 13<Button variant="destructive"> 14 Delete 15</Button> 16 17<Button variant="ghost"> 18 More 19</Button>
This creates a consistent visual language.
Button Sizes
You can also use different sizes:
1<Button size="sm"> 2 Small 3</Button> 4 5<Button size="default"> 6 Default 7</Button> 8 9<Button size="lg"> 10 Large 11</Button>
This is much better than manually creating different button styles throughout the application.
Card
Cards are useful for course platforms.
1import { 2 Card, 3 CardContent, 4 CardDescription, 5 CardHeader, 6 CardTitle, 7} from "@/components/ui/card"; 8 9export function CourseCard() { 10 return ( 11 <Card> 12 <CardHeader> 13 <CardTitle> 14 Next.js Masterclass 15 </CardTitle> 16 17 <CardDescription> 18 Learn Next.js from fundamentals 19 to production architecture. 20 </CardDescription> 21 </CardHeader> 22 23 <CardContent> 24 <Button> 25 Start Learning 26 </Button> 27 </CardContent> 28 </Card> 29 ); 30}
The structure is:
1Card 2├── CardHeader 3│ ├── CardTitle 4│ └── CardDescription 5│ 6└── CardContent 7 └── Button
Dialog
A Dialog displays content over the current page.
For example, a course platform could use it for:
1Confirm enrollment 2Delete course 3Edit profile 4View lesson details
Example:
1import { 2 Dialog, 3 DialogContent, 4 DialogDescription, 5 DialogHeader, 6 DialogTitle, 7 DialogTrigger, 8} from "@/components/ui/dialog"; 9 10import { Button } from "@/components/ui/button"; 11 12export function EnrollmentDialog() { 13 return ( 14 <Dialog> 15 <DialogTrigger asChild> 16 <Button> 17 Enroll Now 18 </Button> 19 </DialogTrigger> 20 21 <DialogContent> 22 <DialogHeader> 23 <DialogTitle> 24 Enroll in Course 25 </DialogTitle> 26 27 <DialogDescription> 28 Confirm that you want to enroll 29 in this course. 30 </DialogDescription> 31 </DialogHeader> 32 33 <Button> 34 Confirm Enrollment 35 </Button> 36 </DialogContent> 37 </Dialog> 38 ); 39}
Architecture:
1Button 2 ↓ 3Open Dialog 4 ↓ 5Course Information 6 ↓ 7Confirm Action
Sheet
A Sheet is useful for side panels.
For example, on mobile:
1┌──────────────────────┐ 2│ Course Dashboard │ 3│ │ 4│ Lesson Content │ 5│ │ 6└──────────────────────┘ 7 ┌─────────────┐ 8 │ Course │ 9 │ Modules │ 10 │ │ 11 │ Topic 1 │ 12 │ Topic 2 │ 13 └─────────────┘
This works well for:
1Mobile navigation 2Course modules 3Filters 4Settings 5Notifications
Example:
1import { 2 Sheet, 3 SheetContent, 4 SheetHeader, 5 SheetTitle, 6 SheetTrigger, 7} from "@/components/ui/sheet"; 8 9import { Button } from "@/components/ui/button"; 10 11export function CourseMenu() { 12 return ( 13 <Sheet> 14 <SheetTrigger asChild> 15 <Button variant="outline"> 16 Modules 17 </Button> 18 </SheetTrigger> 19 20 <SheetContent side="left"> 21 <SheetHeader> 22 <SheetTitle> 23 Course Modules 24 </SheetTitle> 25 </SheetHeader> 26 27 <nav className="mt-6 space-y-2"> 28 <a href="#module-1"> 29 Module 1 30 </a> 31 32 <a href="#module-2"> 33 Module 2 34 </a> 35 </nav> 36 </SheetContent> 37 </Sheet> 38 ); 39}
Dropdown Menu
Dropdowns are useful for user actions.
For example:
1Ankit 2 ↓ 3┌─────────────────┐ 4│ Profile │ 5│ Settings │ 6│ Logout │ 7└─────────────────┘
Example:
1import { 2 DropdownMenu, 3 DropdownMenuContent, 4 DropdownMenuItem, 5 DropdownMenuTrigger, 6} from "@/components/ui/dropdown-menu"; 7 8import { Button } from "@/components/ui/button"; 9 10export function UserMenu() { 11 return ( 12 <DropdownMenu> 13 <DropdownMenuTrigger asChild> 14 <Button variant="outline"> 15 Account 16 </Button> 17 </DropdownMenuTrigger> 18 19 <DropdownMenuContent align="end"> 20 <DropdownMenuItem> 21 Profile 22 </DropdownMenuItem> 23 24 <DropdownMenuItem> 25 Settings 26 </DropdownMenuItem> 27 28 <DropdownMenuItem> 29 Logout 30 </DropdownMenuItem> 31 </DropdownMenuContent> 32 </DropdownMenu> 33 ); 34}
Tabs
Tabs are useful for organizing related content.
For a course page:
1┌──────────┬──────────┬──────────┐ 2│ Overview │ Lessons │ Reviews │ 3└──────────┴──────────┴──────────┘
Example:
1import { 2 Tabs, 3 TabsContent, 4 TabsList, 5 TabsTrigger, 6} from "@/components/ui/tabs"; 7 8export function CourseTabs() { 9 return ( 10 <Tabs defaultValue="overview"> 11 <TabsList> 12 <TabsTrigger value="overview"> 13 Overview 14 </TabsTrigger> 15 16 <TabsTrigger value="lessons"> 17 Lessons 18 </TabsTrigger> 19 20 <TabsTrigger value="reviews"> 21 Reviews 22 </TabsTrigger> 23 </TabsList> 24 25 <TabsContent value="overview"> 26 Course overview 27 </TabsContent> 28 29 <TabsContent value="lessons"> 30 Course lessons 31 </TabsContent> 32 33 <TabsContent value="reviews"> 34 Student reviews 35 </TabsContent> 36 </Tabs> 37 ); 38}
Accordion
An Accordion is excellent for FAQs and course modules.
1import { 2 Accordion, 3 AccordionContent, 4 AccordionItem, 5 AccordionTrigger, 6} from "@/components/ui/accordion"; 7 8export function CourseModules() { 9 return ( 10 <Accordion type="single" collapsible> 11 <AccordionItem value="module-1"> 12 <AccordionTrigger> 13 Module 1 — Next.js Fundamentals 14 </AccordionTrigger> 15 16 <AccordionContent> 17 Introduction to the Next.js App Router. 18 </AccordionContent> 19 </AccordionItem> 20 21 <AccordionItem value="module-2"> 22 <AccordionTrigger> 23 Module 2 — Server Components 24 </AccordionTrigger> 25 26 <AccordionContent> 27 Learn Server Components and 28 server-side rendering. 29 </AccordionContent> 30 </AccordionItem> 31 </Accordion> 32 ); 33}
This creates:
1Module 1 2 ↓ 3Expand 4 ↓ 5Lessons 6 7Module 2 8 ↓ 9Expand 10 ↓ 11Lessons
Input
Input components provide consistent form styling.
1import { Input } from "@/components/ui/input"; 2 3export function CourseSearch() { 4 return ( 5 <Input 6 type="search" 7 placeholder="Search courses..." 8 /> 9 ); 10}
You can combine the Input with a label:
1<div className="space-y-2"> 2 <label htmlFor="email"> 3 Email 4 </label> 5 6 <Input 7 id="email" 8 type="email" 9 placeholder="you@example.com" 10 /> 11</div>
Select
Select components are useful when users need to choose one option.
For example:
1Category 2[ Web Development ▼ ]
Conceptually:
1<Select> 2 <SelectTrigger> 3 <SelectValue placeholder="Select category" /> 4 </SelectTrigger> 5 6 <SelectContent> 7 <SelectItem value="web"> 8 Web Development 9 </SelectItem> 10 11 <SelectItem value="ai"> 12 Artificial Intelligence 13 </SelectItem> 14 15 <SelectItem value="security"> 16 Cybersecurity 17 </SelectItem> 18 </SelectContent> 19</Select>
This is useful for:
1Course category 2Difficulty 3Language 4Sort order 5Status
Table
Tables are particularly useful in admin dashboards.
For example:
1┌────────────┬──────────────┬──────────┐ 2│ Student │ Course │ Progress │ 3├────────────┼──────────────┼──────────┤ 4│ Rahul │ Next.js │ 80% │ 5│ Priya │ TypeScript │ 65% │ 6│ Aman │ React │ 92% │ 7└────────────┴──────────────┴──────────┘
A table component can organize this structure into reusable pieces:
1<Table> 2 <TableHeader> 3 <TableRow> 4 <TableHead> 5 Student 6 </TableHead> 7 8 <TableHead> 9 Course 10 </TableHead> 11 12 <TableHead> 13 Progress 14 </TableHead> 15 </TableRow> 16 </TableHeader> 17 18 <TableBody> 19 <TableRow> 20 <TableCell> 21 Rahul 22 </TableCell> 23 24 <TableCell> 25 Next.js 26 </TableCell> 27 28 <TableCell> 29 80% 30 </TableCell> 31 </TableRow> 32 </TableBody> 33</Table>
Toast
Toast notifications provide temporary feedback.
For example:
1┌──────────────────────────────┐ 2│ Course saved successfully ✓ │ 3└──────────────────────────────┘
Useful events include:
1Course saved 2Profile updated 3Enrollment successful 4Password changed 5Request failed
A toast should provide feedback without forcing the user to leave the current page.
Form Components
shadcn/ui components can be combined with form libraries and validation systems.
A typical architecture is:
1Form 2 ↓ 3Input 4 ↓ 5Validation 6 ↓ 7Submit 8 ↓ 9Server 10 ↓ 11Success / Error
For example:
1<form className="space-y-6"> 2 <div className="space-y-2"> 3 <label htmlFor="title"> 4 Course Title 5 </label> 6 7 <Input 8 id="title" 9 placeholder="Enter course title" 10 /> 11 </div> 12 13 <Button type="submit"> 14 Create Course 15 </Button> 16</form>
For production applications, pair forms with proper server-side validation rather than relying only on browser validation.
Course Dashboard
Now combine the components into a real application.
The target architecture is:
1┌────────────────────────────────────────────┐ 2│ Navbar │ 3├──────────────┬─────────────────────────────┤ 4│ │ │ 5│ Sidebar │ Course Content │ 6│ │ │ 7│ Modules │ Topic │ 8│ │ │ 9│ Topics │ Lesson │ 10│ │ │ 11│ Progress │ Progress │ 12│ │ │ 13└──────────────┴─────────────────────────────┘
Dashboard Structure
A practical component structure:
1components/ 2├── ui/ 3│ ├── button.tsx 4│ ├── card.tsx 5│ ├── dialog.tsx 6│ ├── sheet.tsx 7│ ├── tabs.tsx 8│ ├── accordion.tsx 9│ ├── input.tsx 10│ ├── select.tsx 11│ ├── table.tsx 12│ └── ... 13│ 14├── dashboard/ 15│ ├── navbar.tsx 16│ ├── sidebar.tsx 17│ ├── course-content.tsx 18│ ├── course-progress.tsx 19│ └── lesson-navigation.tsx 20│ 21└── courses/ 22 ├── course-card.tsx 23 └── course-filters.tsx
This gives you three layers:
1shadcn/ui primitives 2 ↓ 3Application components 4 ↓ 5Pages
Dashboard Navbar
1import { Button } from "@/components/ui/button"; 2 3export function DashboardNavbar() { 4 return ( 5 <header className="flex h-16 items-center justify-between border-b px-4"> 6 <div className="font-semibold"> 7 Tech3Space 8 </div> 9 10 <div className="flex items-center gap-3"> 11 <Button variant="ghost"> 12 Notifications 13 </Button> 14 15 <Button variant="outline"> 16 Profile 17 </Button> 18 </div> 19 </header> 20 ); 21}
Dashboard Sidebar
1import { Button } from "@/components/ui/button"; 2 3export function DashboardSidebar() { 4 return ( 5 <aside className="hidden w-64 border-r p-4 md:block"> 6 <h2 className="mb-6 font-semibold"> 7 Course Modules 8 </h2> 9 10 <nav className="space-y-1"> 11 <Button 12 variant="ghost" 13 className="w-full justify-start" 14 > 15 Module 1 16 </Button> 17 18 <Button 19 variant="ghost" 20 className="w-full justify-start" 21 > 22 Module 2 23 </Button> 24 25 <Button 26 variant="ghost" 27 className="w-full justify-start" 28 > 29 Module 3 30 </Button> 31 </nav> 32 </aside> 33 ); 34}
Course Content
1import { 2 Card, 3 CardContent, 4 CardHeader, 5 CardTitle, 6} from "@/components/ui/card"; 7 8export function CourseContent() { 9 return ( 10 <Card> 11 <CardHeader> 12 <CardTitle> 13 Server Components 14 </CardTitle> 15 </CardHeader> 16 17 <CardContent> 18 <p className="leading-7 text-muted-foreground"> 19 Server Components allow Next.js to 20 render components on the server and 21 reduce unnecessary client-side 22 JavaScript. 23 </p> 24 </CardContent> 25 </Card> 26 ); 27}
Course Progress
A dashboard can display progress using cards and progress components.
For example:
1Course Progress 2 3████████████████░░░░ 80% 4 58 of 10 modules completed
Conceptually:
1<Card> 2 <CardHeader> 3 <CardTitle> 4 Course Progress 5 </CardTitle> 6 </CardHeader> 7 8 <CardContent> 9 <div className="text-3xl font-bold"> 10 80% 11 </div> 12 13 <p className="mt-2 text-sm text-muted-foreground"> 14 8 of 10 modules completed 15 </p> 16 </CardContent> 17</Card>
Responsive Dashboard
Desktop:
1┌─────────────────────────────────────────────┐ 2│ Navbar │ 3├───────────────┬─────────────────────────────┤ 4│ Sidebar │ Course Content │ 5│ │ │ 6│ Modules │ Lesson │ 7│ Topics │ │ 8│ Progress │ │ 9└───────────────┴─────────────────────────────┘
Mobile:
1┌─────────────────────────┐ 2│ Navbar │ 3├─────────────────────────┤ 4│ Course Content │ 5│ │ 6│ Lesson │ 7│ │ 8│ [ Modules ] │ 9└─────────────────────────┘
A Sheet can replace the desktop sidebar on mobile.
1Desktop 2Sidebar 3 4Mobile 5Sheet → Sidebar
This is an excellent combination of shadcn/ui and responsive Tailwind CSS.
shadcn/ui Is Not Your Backend
It is important to understand the boundaries.
shadcn/ui handles:
1Buttons 2Dialogs 3Forms 4Inputs 5Tables 6Tabs 7Menus 8UI interactions
It does not automatically provide:
1Authentication 2Database 3API architecture 4Business logic 5Authorization 6Course data 7Payment processing
Your architecture should therefore remain:
1Next.js 2 ↓ 3Application Logic 4 ↓ 5API / Server Actions 6 ↓ 7Database
while shadcn/ui provides:
1UI Layer
Server and Client Components
Some interactive UI components need client-side behavior.
For example:
1Dialog 2Dropdown 3Tabs 4Sheet 5Interactive Form
may involve client-side state or event handlers.
A simplified architecture is:
1Server Component 2 ↓ 3Application Data 4 ↓ 5Client UI Component 6 ↓ 7User Interaction
Do not make an entire page a Client Component just because one small UI element needs interaction.
Instead, keep the interactive boundary as small as practical.
Example Architecture
1export default async function CoursePage() { 2 const course = await getCourse(); 3 4 return ( 5 <main> 6 <CourseHeader 7 course={course} 8 /> 9 10 <CourseContent 11 course={course} 12 /> 13 14 <EnrollmentDialog 15 courseId={course.id} 16 /> 17 </main> 18 ); 19}
Conceptually:
1CoursePage 2(Server Component) 3 ↓ 4Fetch Course 5 ↓ 6Render Content 7 ↓ 8Interactive Dialog 9(Client Component)
This follows the server-first architecture discussed in earlier modules.
Build Challenge
Build a complete course dashboard using shadcn/ui.
Required Components
1Navbar 2Sidebar 3Course Card 4Dialog 5Sheet 6Dropdown Menu 7Tabs 8Accordion 9Input 10Select 11Table 12Toast 13Form
Dashboard
1┌─────────────────────────────────────────────┐ 2│ Navbar User Menu │ 3├───────────────┬─────────────────────────────┤ 4│ Sidebar │ Course Dashboard │ 5│ │ │ 6│ Modules │ Course Title │ 7│ │ Progress │ 8│ Topics │ │ 9│ │ Tabs │ 10│ Progress │ ┌─────────────────────────┐ │ 11│ │ │ Lesson Content │ │ 12│ │ └─────────────────────────┘ │ 13└───────────────┴─────────────────────────────┘
Production Component Architecture
A scalable application can eventually look like:
1components/ 2│ 3├── ui/ 4│ ├── button.tsx 5│ ├── card.tsx 6│ ├── dialog.tsx 7│ ├── dropdown-menu.tsx 8│ ├── sheet.tsx 9│ ├── tabs.tsx 10│ ├── accordion.tsx 11│ ├── input.tsx 12│ ├── select.tsx 13│ ├── table.tsx 14│ └── toast.tsx 15│ 16├── layout/ 17│ ├── navbar.tsx 18│ ├── sidebar.tsx 19│ └── footer.tsx 20│ 21├── courses/ 22│ ├── course-card.tsx 23│ ├── course-header.tsx 24│ ├── course-sidebar.tsx 25│ └── lesson-content.tsx 26│ 27└── dashboard/ 28 ├── dashboard-header.tsx 29 ├── statistics.tsx 30 ├── progress.tsx 31 └── recent-courses.tsx
The architecture becomes:
1shadcn/ui 2 ↓ 3Reusable UI primitives 4 ↓ 5Application components 6 ↓ 7Feature components 8 ↓ 9Pages
Common Mistakes
Mistake 1 — Treating shadcn/ui as a Complete Backend
shadcn/ui is a UI component system.
It does not replace:
1API 2Database 3Authentication 4Authorization 5Business Logic
Mistake 2 — Modifying Every Component Differently
If every button looks different:
1Button A 2Button B 3Button C 4Button D
your design system becomes inconsistent.
Create a small number of intentional variants.
Mistake 3 — Making Everything a Client Component
Interactive components may need client-side behavior, but static content does not.
Prefer:
1Server Component 2 ↓ 3Small Client Component
instead of:
1Entire Application 2 ↓ 3"use client"
Mistake 4 — Ignoring Accessibility
Interactive components should support:
1Keyboard navigation 2Focus management 3Labels 4Screen readers 5Correct semantics 6Accessible dialogs 7Accessible forms
One of the major benefits of a mature component system is reducing the amount of accessibility behavior you have to implement from scratch.
Mistake 5 — Using Components Without Understanding Them
Do not blindly copy components.
Understand:
1Props 2Variants 3State 4Events 5Accessibility 6Client/server boundary 7Styling
Then customize them for your application.
Module 23 Learning Checklist
After completing this module, you should understand:
- What shadcn/ui is
- Why shadcn/ui is different from traditional component libraries
- Installing shadcn/ui
- Component architecture
ButtonCardDialogSheetDropdownMenuTabsAccordionInputSelectTable- Toast notifications
- Form components
- Tailwind integration
- Reusable UI primitives
- Application-specific components
- Responsive dashboards
- Mobile navigation with Sheet
- Server/Client component boundaries
- Accessibility considerations
- Building a course dashboard
- Creating a scalable UI architecture
Final Mental Model
Think of the relationship like this:
1 Next.js 2 │ 3 ┌───────────┴───────────┐ 4 │ │ 5 Server Components Client Components 6 │ │ 7 └───────────┬───────────┘ 8 ↓ 9 Application 10 ↓ 11 shadcn/ui 12 ↓ 13 Reusable UI Primitives 14 ↓ 15 Tailwind CSS 16 ↓ 17 Design System 18 ↓ 19 Production UI
The key idea is:
Tailwind CSS gives you the styling utilities, while shadcn/ui gives you reusable UI building blocks that you can own, customize, and compose into application-specific components.
For a real course platform, the final architecture becomes:
1Course Dashboard 2 │ 3 ├── Navbar 4 ├── Sidebar 5 ├── Course Content 6 ├── Progress 7 ├── Dialog 8 ├── Tabs 9 ├── Accordion 10 ├── Forms 11 └── Tables 12 ↓ 13 shadcn/ui 14 ↓ 15 Tailwind CSS 16 ↓ 17 Next.js