--- id: frontend-shadcn-radix-patterns title: shadcn/ui / Radix / Headless component patterns category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [frontend, ui, vibe-coding] tech_stack: { language: "TS / React", applicable_to: ["Frontend"] } applied_in: [] aliases: [shadcn/ui, Radix, headless component, accessible component, Aria-Kit, Ark UI] --- # shadcn/ui / Radix Patterns > "Component library install" β†’ "Component code copy". **Radix (a11y primitive) + Tailwind = shadcn/ui**. Customizable. Modern React 의 default. ## πŸ“– 핡심 κ°œλ… - Headless: behavior + a11y, style μ—†μŒ. - shadcn: copy code (npm install X). - Radix / Aria / Ark = primitive. - Tailwind = style. ## πŸ’» μ½”λ“œ νŒ¨ν„΄ ### shadcn/ui setup ```bash npx shadcn@latest init npx shadcn@latest add button dialog dropdown-menu ``` β†’ Code κ°€ `components/ui/` 에 μΆ”κ°€. Yours κ°€ 됨. ### Generated button ```tsx // components/ui/button.tsx import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils'; const buttonVariants = cva( 'inline-flex items-center justify-center rounded-md text-sm font-medium ...', { variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground', outline: 'border border-input bg-background', ghost: 'hover:bg-accent hover:text-accent-foreground', }, size: { default: 'h-10 px-4 py-2', sm: 'h-9 rounded-md px-3', lg: 'h-11 rounded-md px-8', }, }, defaultVariants: { variant: 'default', size: 'default' }, } ); export const Button = React.forwardRef< HTMLButtonElement, React.ButtonHTMLAttributes & VariantProps >(({ className, variant, size, ...props }, ref) => ( Title Description Close ``` β†’ A11y (focus trap, escape, aria) μžλ™. ### asChild pattern ```tsx // 자기 component μ‚¬μš© // β†’ Trigger κ°€ button render X. μžμ‹ 의 button 에 props λ¨Έμ§€. ``` β†’ Style + a11y 곡유 β€” wrapper μ—†μŒ. ### Radix primitives list ``` - Accordion / Alert Dialog / Avatar / Checkbox - Collapsible / Context Menu / Dialog / Dropdown Menu - Hover Card / Label / Menubar / Navigation Menu - Popover / Progress / Radio Group / Scroll Area - Select / Separator / Slider / Switch / Tabs - Toast / Toggle / Tooltip ``` β†’ A11y + keyboard μžλ™. ### Headless UI (Tailwind 의) ```tsx import { Menu } from '@headlessui/react'; Options {({ active }) => Profile} ... ``` ### Ark UI (modern, multi-framework) ```tsx import { Dialog } from '@ark-ui/react'; Open ... ``` β†’ React + Vue + Solid + Svelte 같은 API. ### React Aria ```tsx import { useButton } from '@react-aria/button'; function Button(props) { const ref = useRef(); const { buttonProps } = useButton(props, ref); return ; } ``` β†’ Adobe 의 a11y primitive. ### Tailwind Variants (alternative) ```ts import { tv } from 'tailwind-variants'; const button = tv({ base: 'rounded px-4 py-2', variants: { color: { red: 'bg-red-500', blue: 'bg-blue-500' }, }, }); ); } ``` β†’ shadcn 의 Form / FormField κ°€ wrapper. ### Theme (CSS variables) ```css /* globals.css */ :root { --primary: 222 47% 11%; --primary-foreground: 210 40% 98%; --background: 0 0% 100%; --foreground: 222 47% 11%; } .dark { --primary: 210 40% 98%; --primary-foreground: 222 47% 11%; --background: 222 47% 11%; --foreground: 210 40% 98%; } ``` ```js // tailwind.config.js { theme: { extend: { colors: { primary: 'hsl(var(--primary))', background: 'hsl(var(--background))', }, }, }, } ``` β†’ Light / dark κ°€ CSS var 만. ### Dark mode ```tsx import { ThemeProvider, useTheme } from 'next-themes'; function ThemeToggle() { const { theme, setTheme } = useTheme(); return ; } ``` ### MUI / Chakra / Mantine 비ꡐ ``` MUI: - Material Design - 큰 ecosystem - Bundle 큰 - λ³€κ²½ 어렀움 (theme 만) Chakra: - Style props (sx={{...}}) - μž‘μ€ bundle - 합리 Mantine: - κΉŠμ€ component - 쒋은 hook - TypeScript μΉœν™” shadcn / Radix: - Code own - λ³€κ²½ 자유 - μž‘μ€ bundle - λͺ¨λ˜ default β†’ Modern React = shadcn κ°€ default. ``` ### Bundle size ``` shadcn (Radix + Tailwind): only what μ‚¬μš©. MUI: full library (3+ MB). β†’ shadcn κ°€ 더 μž‘μŒ. ``` ### Server component (RSC) μΉœν™” ```tsx // shadcn primitives κ°€ 'use client' λͺ…μ‹œ. // Server component κ°€ import κ°€λŠ₯ (μžλ™ client). ``` ### Storybook ```tsx // Button.stories.tsx export default { component: Button }; export const Primary = { args: { variant: 'default' } }; export const Destructive = { args: { variant: 'destructive' } }; ``` β†’ shadcn 의 component κ°€ component, storybook μΉœν™”. ### A11y testing ```ts import { axe } from 'jest-axe'; test('button is accessible', async () => { const { container } = render(); expect(await axe(container)).toHaveNoViolations(); }); ``` ### Custom variant ```tsx const buttonVariants = cva('...', { variants: { variant: { // ... existing success: 'bg-green-500 text-white hover:bg-green-600', // μΆ”κ°€ }, }, }); ``` β†’ κΈ°μ‘΄ file μˆ˜μ •. Library upgrade 영ν–₯ X (own code). ### Update strategy ```bash # shadcn 의 μƒˆ version npx shadcn@latest diff button # β†’ 변경점 λ³΄μž„ # Manual merge ``` β†’ Library upgrade κ°€ μžλ™ X β€” but freedom. ## πŸ€” μ˜μ‚¬κ²°μ • κΈ°μ€€ | 상황 | μΆ”μ²œ | |---|---| | Modern React | shadcn/ui + Radix | | λΉ λ₯Έ prototype | shadcn (λͺ¨λ“  κ±° μ¦‰μ‹œ) | | 깊이 customize | Radix only | | μž‘μ€ bundle | Headless UI / Radix | | Multi-framework | Ark UI | | Material Design | MUI | | 폼 | shadcn Form + RHF + Zod | | Style props | Chakra / Mantine | ## ❌ μ•ˆν‹°νŒ¨ν„΄ - **MUI + Tailwind**: 2 css system 좩돌. - **Radix 없이 dialog μž‘μ„±**: a11y / focus / esc 깨짐. - **shadcn κ°€ κ·ΈλŒ€λ‘œ + customize μ•ˆ**: own code 의 κ°€μΉ˜ X. - **CSS variable 없이 dark mode**: class name 폭발. - **A11y test μ—†μŒ**: keyboard / screen reader 깨짐. - **Bundle analyze μ—†μŒ**: 큰 component lib μž μž…. ## πŸ€– LLM ν™œμš© 힌트 - shadcn/ui = "code κ°€ own". - Radix = a11y primitive. - CVA / Tailwind Variants κ°€ variant νŒ¨ν„΄. - React Aria / Headless UI / Ark UI = alternative. ## πŸ”— κ΄€λ ¨ λ¬Έμ„œ - [[React_Headless_UI_Patterns]] - [[Frontend_Tailwind_Architecture]] - [[Frontend_Design_Tokens]]