Skip to content
shadcn-ui logo

shadcn/ui Expert

shadcn-ui

Deep expertise on shadcn/ui component library including installation, customization, theming, and accessibility patterns. Use when building UI with shadcn/ui components, integrating Radix UI primitives, configuring Tailwind CSS v4 themes, or implementing dark mode. Covers Next.js App Router, Page...

oimiragieo/agent-studio0installs43stars

SKILL.md

Full skill instructions

shadcn/​ui Expert

Comprehensive guide to building UIs with shadcn/​ui -- the copy-paste component library built on Radix UI primitives and Tailwind CSS. Components are not installed as a dependency; they are copied into your project for full ownership and customization.

When to Apply

Use this skill when:

  • Adding shadcn/​ui components to a React or Next.js project
  • Customizing component styles, variants, or behavior
  • Setting up Tailwind CSS v4 theming with CSS variables
  • Implementing dark mode with shadcn/​ui
  • Building accessible forms, dialogs, or data tables
  • Choosing between shadcn/​ui components and custom implementations

Core Concepts

shadcn/​ui is NOT a Component Library

shadcn/​ui is a collection of reusable components that you copy into your project. Key differences from traditional libraries:

  • No npm package dependency -- components live in your codebase
  • Full ownership -- modify any component freely
  • Radix UI primitives -- accessible, unstyled headless components under the hood
  • Tailwind CSS -- all styling via utility classes and CSS variables
  • CLI-driven -- npx shadcn@latest add button copies component code

Architecture

Your Project
  components/
    ui/              <- shadcn/​ui components live here
      button.tsx
      dialog.tsx
      input.tsx
      ...
  lib/
    utils.ts         <- cn() utility (clsx + tailwind-merge)

Setup

Next.js App Router (Recommended)

# Initialize shadcn/​ui in existing Next.js project
npx shadcn@latest init

# This creates:
# - components.json (configuration)
# - lib/​utils.ts (cn utility)
# - Tailwind CSS variable theme in globals.css

components.json Configuration:

{
  "$schema": "https://ui.shadcn.com/​schema.json",
  "style": "new-york",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/​globals.css",
    "baseColor": "zinc",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/​components",
    "utils": "@/​lib/​utils",
    "ui": "@/​components/​ui",
    "lib": "@/​lib",
    "hooks": "@/​hooks"
  }
}

Vite + React

# Initialize
npx shadcn@latest init

# Vite requires path aliases in vite.config.ts:
import path from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './​src'),
    },
  },
});

Next.js Pages Router

Same as App Router but set rsc: false in components.json since Pages Router does not support React Server Components.

Adding Components

# Add a single component
npx shadcn@latest add button

# Add multiple components
npx shadcn@latest add button card input label

# Add all components
npx shadcn@latest add --all

# View available components
npx shadcn@latest add --list

The cn() Utility

Every shadcn/​ui component uses cn() for conditional class merging:

// lib/​utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

cn() combines clsx (conditional classes) with tailwind-merge (resolves Tailwind conflicts):

<Button
  className={cn(
    'bg-primary text-white',
    isDisabled && 'opacity-50 cursor-not-allowed',
    size === 'lg' && 'px-8 py-4 text-lg'
  )}
>
  Submit
</​Button>

Theming with CSS Variables

Tailwind CSS v4 Theme Setup

shadcn/​ui uses CSS custom properties for theming, enabling runtime theme switching:

/​* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 240 10% 3.9%;
    --card: 0 0% 100%;
    --card-foreground: 240 10% 3.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 240 10% 3.9%;
    --primary: 240 5.9% 10%;
    --primary-foreground: 0 0% 98%;
    --secondary: 240 4.8% 95.9%;
    --secondary-foreground: 240 5.9% 10%;
    --muted: 240 4.8% 95.9%;
    --muted-foreground: 240 3.8% 46.1%;
    --accent: 240 4.8% 95.9%;
    --accent-foreground: 240 5.9% 10%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 0 0% 98%;
    --border: 240 5.9% 90%;
    --input: 240 5.9% 90%;
    --ring: 240 5.9% 10%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 240 10% 3.9%;
    --foreground: 0 0% 98%;
    --card: 240 10% 3.9%;
    --card-foreground: 0 0% 98%;
    --popover: 240 10% 3.9%;
    --popover-foreground: 0 0% 98%;
    --primary: 0 0% 98%;
    --primary-foreground: 240 5.9% 10%;
    --secondary: 240 3.7% 15.9%;
    --secondary-foreground: 0 0% 98%;
    --muted: 240 3.7% 15.9%;
    --muted-foreground: 240 5% 64.9%;
    --accent: 240 3.7% 15.9%;
    --accent-foreground: 0 0% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 0 0% 98%;
    --border: 240 3.7% 15.9%;
    --input: 240 3.7% 15.9%;
    --ring: 240 4.9% 83.9%;
  }
}

Dark Mode Implementation

Use next-themes for Next.js dark mode:

// app/​providers.tsx
'use client';

import { ThemeProvider } from 'next-themes';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
      {children}
    </​ThemeProvider>
  );
}

// app/​layout.tsx
import { Providers } from './​providers';

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <Providers>{children}</​Providers>
      </​body>
    </​html>
  );
}

Toggle component:

'use client';

import { useTheme } from 'next-themes';
import { Button } from '@/​components/​ui/​button';
import { Moon, Sun } from 'lucide-react';

export function ThemeToggle() {
  const { setTheme, theme } = useTheme();

  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
    >
      <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Toggle theme</​span>
    </​Button>
  );
}

Common Component Patterns

Forms with React Hook Form + Zod

'use client';

import { zodResolver } from '@hookform/​resolvers/​zod';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { Button } from '@/​components/​ui/​button';
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/​components/​ui/​form';
import { Input } from '@/​components/​ui/​input';

const formSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});

export function LoginForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: { email: '', password: '' },
  });

  function onSubmit(values: z.infer<typeof formSchema>) {
    console.log(values);
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</​FormLabel>
              <FormControl>
                <Input placeholder="[email protected]" {...field} />
              </​FormControl>
              <FormMessage />
            </​FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="password"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Password</​FormLabel>
              <FormControl>
                <Input type="password" {...field} />
              </​FormControl>
              <FormMessage />
            </​FormItem>
          )}
        />
        <Button type="submit">Sign In</​Button>
      </​form>
    </​Form>
  );
}

Data Tables with TanStack Table

import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/​components/​ui/​table';
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/​react-table';

interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData, TValue>[];
  data: TData[];
}

export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
  });

  return (
    <div className="rounded-md border">
      <Table>
        <TableHeader>
          {table.getHeaderGroups().map(headerGroup => (
            <TableRow key={headerGroup.id}>
              {headerGroup.headers.map(header => (
                <TableHead key={header.id}>
                  {flexRender(header.column.columnDef.header, header.getContext())}
                </​TableHead>
              ))}
            </​TableRow>
          ))}
        </​TableHeader>
        <TableBody>
          {table.getRowModel().rows.map(row => (
            <TableRow key={row.id}>
              {row.getVisibleCells().map(cell => (
                <TableCell key={cell.id}>
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </​TableCell>
              ))}
            </​TableRow>
          ))}
        </​TableBody>
      </​Table>
    </​div>
  );
}

Responsive Dialog / Drawer Pattern

Use Dialog on desktop and Drawer on mobile:

'use client';

import { useMediaQuery } from '@/​hooks/​use-media-query';
import { Dialog, DialogContent, DialogTrigger } from '@/​components/​ui/​dialog';
import { Drawer, DrawerContent, DrawerTrigger } from '@/​components/​ui/​drawer';
import { Button } from '@/​components/​ui/​button';

export function ResponsiveModal({ children }: { children: React.ReactNode }) {
  const isDesktop = useMediaQuery('(min-width: 768px)');

  if (isDesktop) {
    return (
      <Dialog>
        <DialogTrigger asChild>
          <Button>Open</​Button>
        </​DialogTrigger>
        <DialogContent>{children}</​DialogContent>
      </​Dialog>
    );
  }

  return (
    <Drawer>
      <DrawerTrigger asChild>
        <Button>Open</​Button>
      </​DrawerTrigger>
      <DrawerContent>{children}</​DrawerContent>
    </​Drawer>
  );
}

Accessibility Patterns

shadcn/​ui components are built on Radix UI, which provides:

  • Full keyboard navigation (Tab, Arrow keys, Enter, Escape)
  • ARIA attributes (roles, states, properties)
  • Focus management (trapping, restoration)
  • Screen reader announcements

Key Accessibility Features by Component

ComponentKeyboardARIAFocus Trap
ButtonEnter/​Space to activaterole="button"No
DialogEscape to closerole="dialog", aria-modalYes
Dropdown MenuArrow keys to navigaterole="menu", role="menuitem"Yes
SelectArrow keys, type-aheadrole="listbox", role="option"Yes
TabsArrow keys between tabsrole="tablist", role="tab"No
ToastAuto-announcerole="status", aria-liveNo
TooltipFocus/​hover to showrole="tooltip"No

Custom Accessibility Enhancements

// Always provide labels for interactive elements
<Button aria-label="Close dialog">
  <X className="h-4 w-4" />
</​Button>

// Use sr-only for visual-only content
<span className="sr-only">Loading...</​span>

// Announce dynamic content
<div aria-live="polite" aria-atomic="true">
  {statusMessage}
</​div>

Anti-Patterns

  • Do NOT install shadcn/​ui as an npm package -- use the CLI to copy components
  • Do NOT modify Radix primitives directly -- extend via the shadcn wrapper component
  • Do NOT use hardcoded colors -- always use CSS variable theme tokens
  • Do NOT skip the cn() utility -- it prevents Tailwind class conflicts
  • Do NOT forget suppressHydrationWarning on <html> when using next-themes
  • Do NOT nest interactive elements (button inside button, link inside button)

Iron Laws

  1. NEVER install shadcn/​ui as a package dependency — components must be copied into the project for full ownership
  2. ALWAYS use the cn() utility for conditional class names to prevent Tailwind class conflicts
  3. NEVER hardcode colors — always use CSS variable theme tokens for theming consistency
  4. ALWAYS use Radix UI primitives through the shadcn/​ui abstraction, not directly
  5. NEVER nest interactive elements (button inside button, link inside button) — violates accessibility standards

Anti-Patterns

Anti-PatternWhy It FailsCorrect Approach
Installing as a packageComponent source is locked; no customization possibleUse npx shadcn@latest add to copy components into your project
Hardcoding color valuesTheme switching breaks; dark mode failsUse CSS variable tokens (bg-background, text-foreground, etc.)
Skipping cn() utilityTailwind class conflicts produce unpredictable stylesAlways merge classes with cn() from @/​lib/​utils
Direct Radix UI primitive useMissing shadcn styling and accessibility wiringUse shadcn components that wrap Radix primitives with correct classes
Missing suppressHydrationWarningHydration mismatch errors with next-themes dark modeAdd suppressHydrationWarning to <html> when using next-themes

Memory Protocol (MANDATORY)

Before starting: Read .claude/​context/​memory/​learnings.md

After completing:

  • New pattern -> .claude/​context/​memory/​learnings.md
  • Issue found -> .claude/​context/​memory/​issues.md
  • Decision made -> .claude/​context/​memory/​decisions.md

ASSUME INTERRUPTION: If it's not in memory, it didn't happen.

References

More skills from oimiragieo

cloud-devops-expert logo
oimiragieo/agent-studio

cloud-devops-expert

Cloud and DevOps expert including AWS, GCP, Azure, and Terraform

43 0
View
xlsx logo
oimiragieo/agent-studio

xlsx

Spreadsheet toolkit (.xlsx/.csv). Create/edit with formulas/formatting, analyze data, visualization, recalculate formulas, for spreadsheet processing and analysis.

43 0
View
project-analyzer logo
oimiragieo/agent-studio

project-analyzer

Automated brownfield codebase analysis with weighted keyword scoring, three-stream analysis, and design pattern recognition. Detects project type, frameworks, dependencies, architecture patterns, and generates comprehensive project profile.

43 0
View
readme logo
oimiragieo/agent-studio

readme

Use when creating, updating, or generating README and documentation files for projects and libraries

43 0
View
scientific-writing logo
oimiragieo/agent-studio

scientific-writing

Core skill for the deep research and writing tool. Write scientific manuscripts in full paragraphs (never bullet points). Use two-stage process with (1) section outlines with key points using research-lookup then (2) convert to flowing prose. IMRAD structure, citations (APA/AMA/Vancouver), figure...

43 0
View
android-expert logo
oimiragieo/agent-studio

android-expert

Comprehensive Android development expert covering Jetpack Compose, Kotlin coroutines/Flow, Architecture Components, Hilt DI, Navigation, testing, performance, Material Design 3, and modern Android patterns (MVI, Clean Architecture).

43 0
View
biorxiv-database logo
oimiragieo/agent-studio

biorxiv-database

Efficient database search tool for bioRxiv preprint server. Use this skill when searching for life sciences preprints by keywords, authors, date ranges, or categories, retrieving paper metadata, downloading PDFs, or conducting literature reviews.

43 0
View
omega-gemini-cli logo
oimiragieo/agent-studio

omega-gemini-cli

Use when the user wants to use Google Gemini for analysis, large files or codebases, sandbox execution, or brainstorming. Uses headless Gemini CLI scripts (no MCP). Triggers on "use Gemini", "analyze with Gemini", "large file", "sandbox", "brainstorm with Gemini".

43 0
View
gtars logo
oimiragieo/agent-studio

gtars

High-performance toolkit for genomic interval analysis in Rust with Python bindings. Use when working with genomic regions, BED files, coverage tracks, overlap detection, tokenization for ML models, or fragment analysis in computational genomics and machine learning applications.

43 0
View
service-class-conventions logo
oimiragieo/agent-studio

service-class-conventions

Defines the structure and implementation of service classes, enforcing the use of interfaces, ServiceImpl classes, DTOs for data transfer, and transactional management.

43 0
View
diffdock logo
oimiragieo/agent-studio

diffdock

Diffusion-based molecular docking. Predict protein-ligand binding poses from PDB/SMILES, confidence scores, virtual screening, for structure-based drug design. Not for affinity prediction.

43 0
View
skill-discovery logo
oimiragieo/agent-studio

skill-discovery

How agents discover and use skills. Use to understand skill invocation protocol.

43 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View