[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6EMQ9NEYQSW23NAYEWR32TR":3,"skill-same:01M6EMQ9NEYQSW23NAYEWR32TR":75},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":40,"linkOnly":41,"markdownContent":42,"markdownBody":43,"frontMatter":44,"refName":46,"commitSha":47,"skillDirectory":48,"forksCount":49,"repositoryHtmlUrl":50,"repositoryHomepageUrl":51,"ownerHtmlUrl":52,"ownerBlogUrl":6,"ownerDescription":53,"updatedAt":54,"install":55,"resources":68},"01M6EMQ9NEYQSW23NAYEWR32TR","shadcn-ui-patterns",null,"Use when building UI components. Enforces ShadCN UI patterns, accessibility standards (Radix UI), and TailwindCSS best practices for November 2025.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fbarnhardt-enterprises-inc\u002Fshadcn-ui-patterns\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39],"shadcn","ui","shadcn ui","patterns","shadcn ui patterns","tailwindcss","accessibility","components","best practices","grep","read","radix ui","use","standards","ui components","2025","2026-11-10 10:12:30",false,"---\nname: shadcn-ui-patterns\ndescription: Use when building UI components. Enforces ShadCN UI patterns, accessibility standards (Radix UI), and TailwindCSS best practices for November 2025.\nallowed-tools: Read, Grep, Glob\n---\n\n# ShadCN UI Patterns - November 2025 Standards\n\n## When to Use\n- Building new UI components\n- Refactoring existing components to use ShadCN\n- Implementing forms with validation\n- Creating modals, dialogs, and overlays\n- Ensuring accessibility compliance\n\n## Why ShadCN UI?\n- **Copy-paste, not npm** - Full ownership of component code\n- **Radix UI primitives** - Accessibility built-in (WCAG 2.1 AA compliant)\n- **TailwindCSS-first** - Full customization, no CSS-in-JS\n- **TypeScript-native** - Type-safe props and variants\n- **Server Component compatible** - Works with Next.js 15 App Router\n\n## Core Principles\n\n### 1. Component Installation Pattern\n```bash\n# Install individual components as needed\nnpx shadcn@latest add button\nnpx shadcn@latest add dialog\nnpx shadcn@latest add form\nnpx shadcn@latest add input\nnpx shadcn@latest add label\n```\n\nComponents are copied to `src\u002Fcomponents\u002Fui\u002F` directory - you own the code.\n\n### 2. Component Usage Patterns\n\n#### Button Component\n```typescript\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\n\n\u002F\u002F ✅ DO: Use semantic variants\n\u003CButton variant=\"default\">Save\u003C\u002FButton>\n\u003CButton variant=\"destructive\">Delete\u003C\u002FButton>\n\u003CButton variant=\"outline\">Cancel\u003C\u002FButton>\n\u003CButton variant=\"ghost\">Skip\u003C\u002FButton>\n\u003CButton variant=\"link\">Learn More\u003C\u002FButton>\n\n\u002F\u002F ✅ DO: Use size variants\n\u003CButton size=\"default\">Medium\u003C\u002FButton>\n\u003CButton size=\"sm\">Small\u003C\u002FButton>\n\u003CButton size=\"lg\">Large\u003C\u002FButton>\n\u003CButton size=\"icon\">\u003CIcon \u002F>\u003C\u002FButton>\n\n\u002F\u002F ❌ DON'T: Create custom buttons without using Button component\n\u003Cbutton className=\"px-4 py-2 bg-blue-500\">Bad\u003C\u002Fbutton>\n```\n\n#### Dialog\u002FModal Component\n```typescript\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@\u002Fcomponents\u002Fui\u002Fdialog\"\n\n\u002F\u002F ✅ DO: Use proper dialog structure (accessibility)\n\u003CDialog>\n  \u003CDialogTrigger asChild>\n    \u003CButton>Open Settings\u003C\u002FButton>\n  \u003C\u002FDialogTrigger>\n  \u003CDialogContent>\n    \u003CDialogHeader>\n      \u003CDialogTitle>Settings\u003C\u002FDialogTitle>\n      \u003CDialogDescription>\n        Configure your application settings here.\n      \u003C\u002FDialogDescription>\n    \u003C\u002FDialogHeader>\n    {\u002F* Dialog content *\u002F}\n  \u003C\u002FDialogContent>\n\u003C\u002FDialog>\n\n\u002F\u002F ❌ DON'T: Skip DialogHeader or DialogTitle (breaks screen readers)\n\u003CDialogContent>\n  \u003Ch2>Settings\u003C\u002Fh2> {\u002F* Wrong - use DialogTitle *\u002F}\n\u003C\u002FDialogContent>\n```\n\n#### Form Component (with React Hook Form + Zod)\n```typescript\nimport { useForm } from \"react-hook-form\"\nimport { zodResolver } from \"@hookform\u002Fresolvers\u002Fzod\"\nimport * as z from \"zod\"\nimport {\n  Form,\n  FormControl,\n  FormDescription,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from \"@\u002Fcomponents\u002Fui\u002Fform\"\nimport { Input } from \"@\u002Fcomponents\u002Fui\u002Finput\"\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\n\n\u002F\u002F ✅ DO: Define Zod schema first (validation)\nconst formSchema = z.object({\n  email: z.string().email(\"Invalid email address\"),\n  password: z.string().min(8, \"Password must be at least 8 characters\"),\n})\n\nfunction LoginForm() {\n  const form = useForm\u003Cz.infer\u003Ctypeof formSchema>>({\n    resolver: zodResolver(formSchema),\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n  })\n\n  async function onSubmit(values: z.infer\u003Ctypeof formSchema>) {\n    \u002F\u002F Type-safe validated data\n    console.log(values)\n  }\n\n  return (\n    \u003CForm {...form}>\n      \u003Cform onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n        \u003CFormField\n          control={form.control}\n          name=\"email\"\n          render={({ field }) => (\n            \u003CFormItem>\n              \u003CFormLabel>Email\u003C\u002FFormLabel>\n              \u003CFormControl>\n                \u003CInput placeholder=\"you@example.com\" {...field} \u002F>\n              \u003C\u002FFormControl>\n              \u003CFormDescription>\n                We'll never share your email.\n              \u003C\u002FFormDescription>\n              \u003CFormMessage \u002F>\n            \u003C\u002FFormItem>\n          )}\n        \u002F>\n\n        \u003CFormField\n          control={form.control}\n          name=\"password\"\n          render={({ field }) => (\n            \u003CFormItem>\n              \u003CFormLabel>Password\u003C\u002FFormLabel>\n              \u003CFormControl>\n                \u003CInput type=\"password\" {...field} \u002F>\n              \u003C\u002FFormControl>\n              \u003CFormMessage \u002F>\n            \u003C\u002FFormItem>\n          )}\n        \u002F>\n\n        \u003CButton type=\"submit\">Sign In\u003C\u002FButton>\n      \u003C\u002Fform>\n    \u003C\u002FForm>\n  )\n}\n\n\u002F\u002F ❌ DON'T: Use uncontrolled forms without validation\n\u003Cform>\n  \u003Cinput name=\"email\" \u002F> {\u002F* No validation *\u002F}\n\u003C\u002Fform>\n```\n\n### 3. Server vs Client Components\n\n```typescript\n\u002F\u002F ✅ DO: Use Server Component for static dialogs\nimport { Dialog, DialogContent } from \"@\u002Fcomponents\u002Fui\u002Fdialog\"\n\nexport default function ServerDialog() {\n  \u002F\u002F No 'use client' needed\n  return \u003CDialog>...\u003C\u002FDialog>\n}\n\n\u002F\u002F ✅ DO: Use Client Component when state is needed\n'use client'\n\nimport { useState } from 'react'\nimport { Dialog, DialogContent } from \"@\u002Fcomponents\u002Fui\u002Fdialog\"\n\nexport function ClientDialog() {\n  const [open, setOpen] = useState(false)\n\n  return (\n    \u003CDialog open={open} onOpenChange={setOpen}>\n      \u003CDialogContent>...\u003C\u002FDialogContent>\n    \u003C\u002FDialog>\n  )\n}\n```\n\n### 4. Accessibility Requirements\n\n#### Focus Management\n```typescript\n\u002F\u002F ✅ DO: Use DialogTrigger with asChild for proper focus\n\u003CDialogTrigger asChild>\n  \u003CButton>Open\u003C\u002FButton>\n\u003C\u002FDialogTrigger>\n\n\u002F\u002F ❌ DON'T: Manually trigger without proper focus handling\n\u003CButton onClick={() => setOpen(true)}>Open\u003C\u002FButton>\n```\n\n#### Keyboard Navigation\n```typescript\n\u002F\u002F ✅ ShadCN handles this automatically:\n\u002F\u002F - ESC closes dialogs\n\u002F\u002F - Tab navigates focusable elements\n\u002F\u002F - Enter\u002FSpace activates buttons\n\u002F\u002F - Arrow keys navigate menus\n\n\u002F\u002F ❌ DON'T: Override default keyboard behavior without good reason\n```\n\n#### Screen Reader Support\n```typescript\n\u002F\u002F ✅ DO: Always include DialogTitle (required for ARIA)\n\u003CDialogHeader>\n  \u003CDialogTitle>Delete Project\u003C\u002FDialogTitle>\n  \u003CDialogDescription>\n    This action cannot be undone.\n  \u003C\u002FDialogDescription>\n\u003C\u002FDialogHeader>\n\n\u002F\u002F ❌ DON'T: Use visually hidden titles incorrectly\n\u003CDialogTitle className=\"sr-only\">Delete\u003C\u002FDialogTitle>\n\u002F\u002F Only hide if there's a clear visual alternative\n```\n\n### 5. Common Components to Use\n\n| Component | Use Case | Key Props |\n|-----------|----------|-----------|\n| `Button` | All clickable actions | `variant`, `size`, `asChild` |\n| `Dialog` | Modals, confirmations | `open`, `onOpenChange` |\n| `Sheet` | Side panels, drawers | `side`, `open`, `onOpenChange` |\n| `Popover` | Tooltips, menus | `open`, `onOpenChange` |\n| `Form` | All forms | `form` (from useForm) |\n| `Input` | Text input | `type`, `placeholder` |\n| `Select` | Dropdowns | `value`, `onValueChange` |\n| `Checkbox` | Boolean input | `checked`, `onCheckedChange` |\n| `RadioGroup` | Single choice | `value`, `onValueChange` |\n| `Table` | Data tables | `table` (from TanStack Table) |\n| `Card` | Content containers | `CardHeader`, `CardContent`, `CardFooter` |\n| `Toast` | Notifications | `title`, `description`, `variant` |\n| `Command` | Command palette | `onSelect` |\n| `Tabs` | Tab navigation | `value`, `onValueChange` |\n\n### 6. TailwindCSS Best Practices\n\n```typescript\n\u002F\u002F ✅ DO: Use Tailwind utility classes\n\u003CButton className=\"w-full mt-4\">Submit\u003C\u002FButton>\n\n\u002F\u002F ✅ DO: Use cn() helper for conditional classes\nimport { cn } from \"@\u002Flib\u002Futils\"\n\n\u003CButton className={cn(\n  \"w-full\",\n  isLoading && \"opacity-50 cursor-not-allowed\"\n)}>\n  Submit\n\u003C\u002FButton>\n\n\u002F\u002F ❌ DON'T: Use inline styles\n\u003CButton style={{ width: '100%', marginTop: '16px' }}>Submit\u003C\u002FButton>\n\n\u002F\u002F ❌ DON'T: Create custom CSS files for components\n\u002F\u002F styles.css\n.my-button { width: 100%; }\n```\n\n### 7. Dark Mode Support\n\n```typescript\n\u002F\u002F ✅ DO: Use Tailwind dark mode classes\n\u003Cdiv className=\"bg-white dark:bg-gray-900 text-black dark:text-white\">\n  Content\n\u003C\u002Fdiv>\n\n\u002F\u002F ✅ ShadCN components have dark mode built-in\n\u003CButton variant=\"default\">\n  {\u002F* Automatically styled for dark mode *\u002F}\n\u003C\u002FButton>\n```\n\n## Common Mistakes to Catch\n\n### ❌ Missing DialogTitle (Accessibility Violation)\n```typescript\n\u002F\u002F BAD\n\u003CDialogContent>\n  \u003Ch2>Settings\u003C\u002Fh2>\n  \u003Cp>Content\u003C\u002Fp>\n\u003C\u002FDialogContent>\n\n\u002F\u002F GOOD\n\u003CDialogContent>\n  \u003CDialogHeader>\n    \u003CDialogTitle>Settings\u003C\u002FDialogTitle>\n  \u003C\u002FDialogHeader>\n  \u003Cp>Content\u003C\u002Fp>\n\u003C\u002FDialogContent>\n```\n\n### ❌ Not Using Form Component for Forms\n```typescript\n\u002F\u002F BAD - No validation, poor UX\n\u003Cform>\n  \u003Cinput name=\"email\" \u002F>\n  \u003Cbutton type=\"submit\">Submit\u003C\u002Fbutton>\n\u003C\u002Fform>\n\n\u002F\u002F GOOD - Validation, error messages, accessibility\n\u003CForm {...form}>\n  \u003Cform onSubmit={form.handleSubmit(onSubmit)}>\n    \u003CFormField name=\"email\" ... \u002F>\n  \u003C\u002Fform>\n\u003C\u002FForm>\n```\n\n### ❌ Hardcoding Colors Instead of Using Variants\n```typescript\n\u002F\u002F BAD\n\u003CButton className=\"bg-red-500 hover:bg-red-600\">Delete\u003C\u002FButton>\n\n\u002F\u002F GOOD\n\u003CButton variant=\"destructive\">Delete\u003C\u002FButton>\n```\n\n### ❌ Not Using asChild for Triggers\n```typescript\n\u002F\u002F BAD - Creates unnecessary nested buttons\n\u003CDialogTrigger>\n  \u003CButton>Open\u003C\u002FButton>\n\u003C\u002FDialogTrigger>\n\u002F\u002F Renders: \u003Cbutton>\u003Cbutton>Open\u003C\u002Fbutton>\u003C\u002Fbutton> (invalid HTML)\n\n\u002F\u002F GOOD - Merges props into single button\n\u003CDialogTrigger asChild>\n  \u003CButton>Open\u003C\u002FButton>\n\u003C\u002FDialogTrigger>\n\u002F\u002F Renders: \u003Cbutton>Open\u003C\u002Fbutton>\n```\n\n## Testing ShadCN Components\n\n```typescript\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\nimport { Dialog, DialogTrigger, DialogContent } from '@\u002Fcomponents\u002Fui\u002Fdialog'\n\ndescribe('Dialog', () => {\n  it('should open when trigger is clicked', async () => {\n    const user = userEvent.setup()\n\n    render(\n      \u003CDialog>\n        \u003CDialogTrigger asChild>\n          \u003Cbutton>Open\u003C\u002Fbutton>\n        \u003C\u002FDialogTrigger>\n        \u003CDialogContent>\n          \u003Cdiv>Dialog content\u003C\u002Fdiv>\n        \u003C\u002FDialogContent>\n      \u003C\u002FDialog>\n    )\n\n    \u002F\u002F Dialog content should not be visible initially\n    expect(screen.queryByText('Dialog content')).not.toBeInTheDocument()\n\n    \u002F\u002F Click trigger\n    await user.click(screen.getByText('Open'))\n\n    \u002F\u002F Dialog content should now be visible\n    expect(screen.getByText('Dialog content')).toBeInTheDocument()\n  })\n\n  it('should close on ESC key', async () => {\n    const user = userEvent.setup()\n\n    render(\n      \u003CDialog defaultOpen>\n        \u003CDialogContent>Dialog content\u003C\u002FDialogContent>\n      \u003C\u002FDialog>\n    )\n\n    expect(screen.getByText('Dialog content')).toBeInTheDocument()\n\n    await user.keyboard('{Escape}')\n\n    expect(screen.queryByText('Dialog content')).not.toBeInTheDocument()\n  })\n})\n```\n\n## Resources\n\n- **Official Docs**: https:\u002F\u002Fui.shadcn.com\n- **Radix UI**: https:\u002F\u002Fwww.radix-ui.com\n- **Examples**: https:\u002F\u002Fui.shadcn.com\u002Fexamples\n- **Themes**: https:\u002F\u002Fui.shadcn.com\u002Fthemes\n\n## November 2025 Note\n\nShadCN UI is the industry standard for React component libraries as of November 2025. All new Quetrex applications must use ShadCN UI for consistency, accessibility, and maintainability.\n","# ShadCN UI Patterns - November 2025 Standards\n\n## When to Use\n- Building new UI components\n- Refactoring existing components to use ShadCN\n- Implementing forms with validation\n- Creating modals, dialogs, and overlays\n- Ensuring accessibility compliance\n\n## Why ShadCN UI?\n- **Copy-paste, not npm** - Full ownership of component code\n- **Radix UI primitives** - Accessibility built-in (WCAG 2.1 AA compliant)\n- **TailwindCSS-first** - Full customization, no CSS-in-JS\n- **TypeScript-native** - Type-safe props and variants\n- **Server Component compatible** - Works with Next.js 15 App Router\n\n## Core Principles\n\n### 1. Component Installation Pattern\n```bash\n# Install individual components as needed\nnpx shadcn@latest add button\nnpx shadcn@latest add dialog\nnpx shadcn@latest add form\nnpx shadcn@latest add input\nnpx shadcn@latest add label\n```\n\nComponents are copied to `src\u002Fcomponents\u002Fui\u002F` directory - you own the code.\n\n### 2. Component Usage Patterns\n\n#### Button Component\n```typescript\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\n\n\u002F\u002F ✅ DO: Use semantic variants\n\u003CButton variant=\"default\">Save\u003C\u002FButton>\n\u003CButton variant=\"destructive\">Delete\u003C\u002FButton>\n\u003CButton variant=\"outline\">Cancel\u003C\u002FButton>\n\u003CButton variant=\"ghost\">Skip\u003C\u002FButton>\n\u003CButton variant=\"link\">Learn More\u003C\u002FButton>\n\n\u002F\u002F ✅ DO: Use size variants\n\u003CButton size=\"default\">Medium\u003C\u002FButton>\n\u003CButton size=\"sm\">Small\u003C\u002FButton>\n\u003CButton size=\"lg\">Large\u003C\u002FButton>\n\u003CButton size=\"icon\">\u003CIcon \u002F>\u003C\u002FButton>\n\n\u002F\u002F ❌ DON'T: Create custom buttons without using Button component\n\u003Cbutton className=\"px-4 py-2 bg-blue-500\">Bad\u003C\u002Fbutton>\n```\n\n#### Dialog\u002FModal Component\n```typescript\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@\u002Fcomponents\u002Fui\u002Fdialog\"\n\n\u002F\u002F ✅ DO: Use proper dialog structure (accessibility)\n\u003CDialog>\n  \u003CDialogTrigger asChild>\n    \u003CButton>Open Settings\u003C\u002FButton>\n  \u003C\u002FDialogTrigger>\n  \u003CDialogContent>\n    \u003CDialogHeader>\n      \u003CDialogTitle>Settings\u003C\u002FDialogTitle>\n      \u003CDialogDescription>\n        Configure your application settings here.\n      \u003C\u002FDialogDescription>\n    \u003C\u002FDialogHeader>\n    {\u002F* Dialog content *\u002F}\n  \u003C\u002FDialogContent>\n\u003C\u002FDialog>\n\n\u002F\u002F ❌ DON'T: Skip DialogHeader or DialogTitle (breaks screen readers)\n\u003CDialogContent>\n  \u003Ch2>Settings\u003C\u002Fh2> {\u002F* Wrong - use DialogTitle *\u002F}\n\u003C\u002FDialogContent>\n```\n\n#### Form Component (with React Hook Form + Zod)\n```typescript\nimport { useForm } from \"react-hook-form\"\nimport { zodResolver } from \"@hookform\u002Fresolvers\u002Fzod\"\nimport * as z from \"zod\"\nimport {\n  Form,\n  FormControl,\n  FormDescription,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from \"@\u002Fcomponents\u002Fui\u002Fform\"\nimport { Input } from \"@\u002Fcomponents\u002Fui\u002Finput\"\nimport { Button } from \"@\u002Fcomponents\u002Fui\u002Fbutton\"\n\n\u002F\u002F ✅ DO: Define Zod schema first (validation)\nconst formSchema = z.object({\n  email: z.string().email(\"Invalid email address\"),\n  password: z.string().min(8, \"Password must be at least 8 characters\"),\n})\n\nfunction LoginForm() {\n  const form = useForm\u003Cz.infer\u003Ctypeof formSchema>>({\n    resolver: zodResolver(formSchema),\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n  })\n\n  async function onSubmit(values: z.infer\u003Ctypeof formSchema>) {\n    \u002F\u002F Type-safe validated data\n    console.log(values)\n  }\n\n  return (\n    \u003CForm {...form}>\n      \u003Cform onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n        \u003CFormField\n          control={form.control}\n          name=\"email\"\n          render={({ field }) => (\n            \u003CFormItem>\n              \u003CFormLabel>Email\u003C\u002FFormLabel>\n              \u003CFormControl>\n                \u003CInput placeholder=\"you@example.com\" {...field} \u002F>\n              \u003C\u002FFormControl>\n              \u003CFormDescription>\n                We'll never share your email.\n              \u003C\u002FFormDescription>\n              \u003CFormMessage \u002F>\n            \u003C\u002FFormItem>\n          )}\n        \u002F>\n\n        \u003CFormField\n          control={form.control}\n          name=\"password\"\n          render={({ field }) => (\n            \u003CFormItem>\n              \u003CFormLabel>Password\u003C\u002FFormLabel>\n              \u003CFormControl>\n                \u003CInput type=\"password\" {...field} \u002F>\n              \u003C\u002FFormControl>\n              \u003CFormMessage \u002F>\n            \u003C\u002FFormItem>\n          )}\n        \u002F>\n\n        \u003CButton type=\"submit\">Sign In\u003C\u002FButton>\n      \u003C\u002Fform>\n    \u003C\u002FForm>\n  )\n}\n\n\u002F\u002F ❌ DON'T: Use uncontrolled forms without validation\n\u003Cform>\n  \u003Cinput name=\"email\" \u002F> {\u002F* No validation *\u002F}\n\u003C\u002Fform>\n```\n\n### 3. Server vs Client Components\n\n```typescript\n\u002F\u002F ✅ DO: Use Server Component for static dialogs\nimport { Dialog, DialogContent } from \"@\u002Fcomponents\u002Fui\u002Fdialog\"\n\nexport default function ServerDialog() {\n  \u002F\u002F No 'use client' needed\n  return \u003CDialog>...\u003C\u002FDialog>\n}\n\n\u002F\u002F ✅ DO: Use Client Component when state is needed\n'use client'\n\nimport { useState } from 'react'\nimport { Dialog, DialogContent } from \"@\u002Fcomponents\u002Fui\u002Fdialog\"\n\nexport function ClientDialog() {\n  const [open, setOpen] = useState(false)\n\n  return (\n    \u003CDialog open={open} onOpenChange={setOpen}>\n      \u003CDialogContent>...\u003C\u002FDialogContent>\n    \u003C\u002FDialog>\n  )\n}\n```\n\n### 4. Accessibility Requirements\n\n#### Focus Management\n```typescript\n\u002F\u002F ✅ DO: Use DialogTrigger with asChild for proper focus\n\u003CDialogTrigger asChild>\n  \u003CButton>Open\u003C\u002FButton>\n\u003C\u002FDialogTrigger>\n\n\u002F\u002F ❌ DON'T: Manually trigger without proper focus handling\n\u003CButton onClick={() => setOpen(true)}>Open\u003C\u002FButton>\n```\n\n#### Keyboard Navigation\n```typescript\n\u002F\u002F ✅ ShadCN handles this automatically:\n\u002F\u002F - ESC closes dialogs\n\u002F\u002F - Tab navigates focusable elements\n\u002F\u002F - Enter\u002FSpace activates buttons\n\u002F\u002F - Arrow keys navigate menus\n\n\u002F\u002F ❌ DON'T: Override default keyboard behavior without good reason\n```\n\n#### Screen Reader Support\n```typescript\n\u002F\u002F ✅ DO: Always include DialogTitle (required for ARIA)\n\u003CDialogHeader>\n  \u003CDialogTitle>Delete Project\u003C\u002FDialogTitle>\n  \u003CDialogDescription>\n    This action cannot be undone.\n  \u003C\u002FDialogDescription>\n\u003C\u002FDialogHeader>\n\n\u002F\u002F ❌ DON'T: Use visually hidden titles incorrectly\n\u003CDialogTitle className=\"sr-only\">Delete\u003C\u002FDialogTitle>\n\u002F\u002F Only hide if there's a clear visual alternative\n```\n\n### 5. Common Components to Use\n\n| Component | Use Case | Key Props |\n|-----------|----------|-----------|\n| `Button` | All clickable actions | `variant`, `size`, `asChild` |\n| `Dialog` | Modals, confirmations | `open`, `onOpenChange` |\n| `Sheet` | Side panels, drawers | `side`, `open`, `onOpenChange` |\n| `Popover` | Tooltips, menus | `open`, `onOpenChange` |\n| `Form` | All forms | `form` (from useForm) |\n| `Input` | Text input | `type`, `placeholder` |\n| `Select` | Dropdowns | `value`, `onValueChange` |\n| `Checkbox` | Boolean input | `checked`, `onCheckedChange` |\n| `RadioGroup` | Single choice | `value`, `onValueChange` |\n| `Table` | Data tables | `table` (from TanStack Table) |\n| `Card` | Content containers | `CardHeader`, `CardContent`, `CardFooter` |\n| `Toast` | Notifications | `title`, `description`, `variant` |\n| `Command` | Command palette | `onSelect` |\n| `Tabs` | Tab navigation | `value`, `onValueChange` |\n\n### 6. TailwindCSS Best Practices\n\n```typescript\n\u002F\u002F ✅ DO: Use Tailwind utility classes\n\u003CButton className=\"w-full mt-4\">Submit\u003C\u002FButton>\n\n\u002F\u002F ✅ DO: Use cn() helper for conditional classes\nimport { cn } from \"@\u002Flib\u002Futils\"\n\n\u003CButton className={cn(\n  \"w-full\",\n  isLoading && \"opacity-50 cursor-not-allowed\"\n)}>\n  Submit\n\u003C\u002FButton>\n\n\u002F\u002F ❌ DON'T: Use inline styles\n\u003CButton style={{ width: '100%', marginTop: '16px' }}>Submit\u003C\u002FButton>\n\n\u002F\u002F ❌ DON'T: Create custom CSS files for components\n\u002F\u002F styles.css\n.my-button { width: 100%; }\n```\n\n### 7. Dark Mode Support\n\n```typescript\n\u002F\u002F ✅ DO: Use Tailwind dark mode classes\n\u003Cdiv className=\"bg-white dark:bg-gray-900 text-black dark:text-white\">\n  Content\n\u003C\u002Fdiv>\n\n\u002F\u002F ✅ ShadCN components have dark mode built-in\n\u003CButton variant=\"default\">\n  {\u002F* Automatically styled for dark mode *\u002F}\n\u003C\u002FButton>\n```\n\n## Common Mistakes to Catch\n\n### ❌ Missing DialogTitle (Accessibility Violation)\n```typescript\n\u002F\u002F BAD\n\u003CDialogContent>\n  \u003Ch2>Settings\u003C\u002Fh2>\n  \u003Cp>Content\u003C\u002Fp>\n\u003C\u002FDialogContent>\n\n\u002F\u002F GOOD\n\u003CDialogContent>\n  \u003CDialogHeader>\n    \u003CDialogTitle>Settings\u003C\u002FDialogTitle>\n  \u003C\u002FDialogHeader>\n  \u003Cp>Content\u003C\u002Fp>\n\u003C\u002FDialogContent>\n```\n\n### ❌ Not Using Form Component for Forms\n```typescript\n\u002F\u002F BAD - No validation, poor UX\n\u003Cform>\n  \u003Cinput name=\"email\" \u002F>\n  \u003Cbutton type=\"submit\">Submit\u003C\u002Fbutton>\n\u003C\u002Fform>\n\n\u002F\u002F GOOD - Validation, error messages, accessibility\n\u003CForm {...form}>\n  \u003Cform onSubmit={form.handleSubmit(onSubmit)}>\n    \u003CFormField name=\"email\" ... \u002F>\n  \u003C\u002Fform>\n\u003C\u002FForm>\n```\n\n### ❌ Hardcoding Colors Instead of Using Variants\n```typescript\n\u002F\u002F BAD\n\u003CButton className=\"bg-red-500 hover:bg-red-600\">Delete\u003C\u002FButton>\n\n\u002F\u002F GOOD\n\u003CButton variant=\"destructive\">Delete\u003C\u002FButton>\n```\n\n### ❌ Not Using asChild for Triggers\n```typescript\n\u002F\u002F BAD - Creates unnecessary nested buttons\n\u003CDialogTrigger>\n  \u003CButton>Open\u003C\u002FButton>\n\u003C\u002FDialogTrigger>\n\u002F\u002F Renders: \u003Cbutton>\u003Cbutton>Open\u003C\u002Fbutton>\u003C\u002Fbutton> (invalid HTML)\n\n\u002F\u002F GOOD - Merges props into single button\n\u003CDialogTrigger asChild>\n  \u003CButton>Open\u003C\u002FButton>\n\u003C\u002FDialogTrigger>\n\u002F\u002F Renders: \u003Cbutton>Open\u003C\u002Fbutton>\n```\n\n## Testing ShadCN Components\n\n```typescript\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\nimport { Dialog, DialogTrigger, DialogContent } from '@\u002Fcomponents\u002Fui\u002Fdialog'\n\ndescribe('Dialog', () => {\n  it('should open when trigger is clicked', async () => {\n    const user = userEvent.setup()\n\n    render(\n      \u003CDialog>\n        \u003CDialogTrigger asChild>\n          \u003Cbutton>Open\u003C\u002Fbutton>\n        \u003C\u002FDialogTrigger>\n        \u003CDialogContent>\n          \u003Cdiv>Dialog content\u003C\u002Fdiv>\n        \u003C\u002FDialogContent>\n      \u003C\u002FDialog>\n    )\n\n    \u002F\u002F Dialog content should not be visible initially\n    expect(screen.queryByText('Dialog content')).not.toBeInTheDocument()\n\n    \u002F\u002F Click trigger\n    await user.click(screen.getByText('Open'))\n\n    \u002F\u002F Dialog content should now be visible\n    expect(screen.getByText('Dialog content')).toBeInTheDocument()\n  })\n\n  it('should close on ESC key', async () => {\n    const user = userEvent.setup()\n\n    render(\n      \u003CDialog defaultOpen>\n        \u003CDialogContent>Dialog content\u003C\u002FDialogContent>\n      \u003C\u002FDialog>\n    )\n\n    expect(screen.getByText('Dialog content')).toBeInTheDocument()\n\n    await user.keyboard('{Escape}')\n\n    expect(screen.queryByText('Dialog content')).not.toBeInTheDocument()\n  })\n})\n```\n\n## Resources\n\n- **Official Docs**: https:\u002F\u002Fui.shadcn.com\n- **Radix UI**: https:\u002F\u002Fwww.radix-ui.com\n- **Examples**: https:\u002F\u002Fui.shadcn.com\u002Fexamples\n- **Themes**: https:\u002F\u002Fui.shadcn.com\u002Fthemes\n\n## November 2025 Note\n\nShadCN UI is the industry standard for React component libraries as of November 2025. All new Quetrex applications must use ShadCN UI for consistency, accessibility, and maintainability.\n",{"name":5,"description":7,"allowed-tools":45},"Read, Grep, Glob","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Faiskillstore\u002Fmarketplace\u002Fbarnhardt-enterprises-inc\u002Fshadcn-ui-patterns",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":56,"prompt":66,"zipAvailable":67},[57,60,63],{"tool":58,"command":59},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill shadcn-ui-patterns",{"tool":61,"command":62},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill shadcn-ui-patterns",{"tool":64,"command":65},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill shadcn-ui-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fbarnhardt-enterprises-inc\u002Fshadcn-ui-patterns\u002FSKILL.md",true,[69],{"path":70,"resourceType":71,"resourceTypeDesc":72,"sizeBytes":73,"sourceHtmlUrl":74},"skill-report.json","REFERENCE","参考文档",17202,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fbarnhardt-enterprises-inc\u002Fshadcn-ui-patterns\u002Fskill-report.json",[76,90,100,109,115,122],{"skillNo":77,"name":78,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":86,"publishedAt":40},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[81],{"categoryNo":82,"categoryName":83,"taxonomyType":84,"taxonomyTypeDesc":85},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[87,88,89],"model","model hierarchy","model hierarchy skill",{"skillNo":91,"name":92,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":93,"categories":94,"keywords":96,"publishedAt":40},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[95],{"categoryNo":82,"categoryName":83,"taxonomyType":84,"taxonomyTypeDesc":85},[97,98,99],"MCP","zoom","zoom mcp",{"skillNo":101,"name":102,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":105,"publishedAt":40},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[106,107,108,98],"sdk","zoom apps sdk","apps",{"skillNo":110,"name":111,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":114,"publishedAt":40},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[111],{"skillNo":116,"name":117,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":118,"categories":119,"keywords":121,"publishedAt":40},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[120],{"categoryNo":82,"categoryName":83,"taxonomyType":84,"taxonomyTypeDesc":85},[117],{"skillNo":123,"name":124,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":127,"publishedAt":40},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[124]]