shadcn/ui: Claude Code 활용 가이드
shadcn/ui: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.
shadcn/uiで프로덕션品質のUIを구축する
shadcn/uiは、Radix UIとTailwind CSSをベースにした컴포넌트コレクションです。npm 패키지ではなく、コードを直接프로젝트にコピーして使う설계のため、自由に커스터마이징할 수 있습니다。Claude Code는 컴포넌트の도입から커스터마이징まで強力にサポートします。
初期セットアップ
Claude Code에프로젝트への도입を依頼합시다。
> Next.js프로젝트にshadcn/uiをセットアップして。
> ダークモード대응、日本語폰트설정も含めて。
// tailwind.config.ts
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: ["class"],
content: [
"./src/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
],
theme: {
extend: {
fontFamily: {
sans: [
"Noto Sans JP",
"Inter",
"system-ui",
"sans-serif",
],
},
colors: {
border: "hsl(var(--border))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
};
export default config;
데이터테이블の구축
shadcn/uiのDataTable컴포넌트を커스터마이징する例です。
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
interface DataTableProps<T> {
data: T[];
columns: ColumnDef<T>[];
searchPlaceholder?: string;
onRowClick?: (row: T) => void;
}
function DataTable<T extends { id: string }>({
data,
columns,
searchPlaceholder = "検索...",
onRowClick,
}: DataTableProps<T>) {
const [search, setSearch] = useState("");
const [sorting, setSorting] = useState<SortingState>([]);
const table = useReactTable({
data,
columns,
state: { sorting, globalFilter: search },
onSortingChange: setSorting,
onGlobalFilterChange: setSearch,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
return (
<div>
<div className="flex items-center py-4">
<Input
placeholder={searchPlaceholder}
value={search}
onChange={(e) => setSearch(e.target.value)}
className="max-w-sm"
/>
</div>
<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}
onClick={() => onRowClick?.(row.original)}
className={onRowClick ? "cursor-pointer" : ""}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
前へ
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
次へ
</Button>
</div>
</div>
);
}
폼통합
shadcn/uiのForm컴포넌트とReact Hook Form + Zodの통합です。
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
const profileSchema = z.object({
displayName: z.string().min(2, "2文字以上入力してください"),
email: z.string().email("有効なメールアドレスを入力してください"),
role: z.enum(["developer", "designer", "manager"]),
bio: z.string().max(200).optional(),
});
function ProfileForm() {
const form = useForm<z.infer<typeof profileSchema>>({
resolver: zodResolver(profileSchema),
});
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="displayName"
render={({ field }) => (
<FormItem>
<FormLabel>表示名</FormLabel>
<FormControl>
<Input placeholder="表示名を入力" {...field} />
</FormControl>
<FormDescription>公開プロフィールに表示される名前です。</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="role"
render={({ field }) => (
<FormItem>
<FormLabel>役職</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="役職を選択" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="developer">開発者</SelectItem>
<SelectItem value="designer">デザイナー</SelectItem>
<SelectItem value="manager">マネージャー</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">保存</Button>
</form>
</Form>
);
}
カスタム테마の설계
프로젝트固有の컬러スキームを설정するパターンです。
/* globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222 47% 11%;
--primary: 221 83% 53%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96%;
--secondary-foreground: 222 47% 11%;
--muted: 210 40% 96%;
--muted-foreground: 215 16% 47%;
--accent: 210 40% 96%;
--accent-foreground: 222 47% 11%;
--destructive: 0 84% 60%;
--destructive-foreground: 210 40% 98%;
--border: 214 32% 91%;
--radius: 0.5rem;
}
.dark {
--background: 222 47% 11%;
--foreground: 210 40% 98%;
--primary: 217 91% 60%;
--primary-foreground: 222 47% 11%;
--secondary: 217 33% 17%;
--secondary-foreground: 210 40% 98%;
--muted: 217 33% 17%;
--muted-foreground: 215 20% 65%;
--accent: 217 33% 17%;
--accent-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--border: 217 33% 17%;
}
}
정리
shadcn/uiはコードを프로젝트に直接取り込む설계のため、制約なく自由に커스터마이징할 수 있습니다。Claude Codeを활용すれば、컴포넌트の추가から테마설계、폼통합まで효율적으로구현가능합니다。
Radix UIの基礎はRadix UI컴포넌트활용を、폼구현의 상세 정보는React Hook Form完全攻略를 참고하세요.shadcn/ui공식 사이트도 확인해 두세요.
무료 PDF: 5분 완성 Claude Code 치트시트
이메일 주소만 등록하시면 A4 한 장짜리 치트시트 PDF를 즉시 보내드립니다.
개인정보는 엄격하게 관리하며 스팸은 보내지 않습니다.
이 글을 작성한 사람
Masa
Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.
관련 글
Claude Code 다국어 글을 매일 발행하기 전에 확인할 7가지
누락된 언어, 깨진 CTA, 반영되지 않은 배포를 막기 위해 다국어 Claude Code 글을 매일 발행하기 전에 확인할 체크리스트입니다.
Codex Automations란? 잠자는 동안 AI가 콘텐츠 운영을 처리하게 하는 방법
Codex Automations로 트래픽 분석, 주제 선정, 글 작성, CTA 개선, 배포까지 자동화하는 실전 가이드.
Claude Code × GCP Cloud Functions 완전 가이드 | 서버리스 함수 초고속 개발
Claude Code로 GCP Cloud Functions를 효율화. HTTP/Pub/Sub/Firestore 트리거 구현부터 로컬 테스트·배포 자동화까지, Masa의 실무 경험을 토대로 실제 코드로 해설.