ui-ux-consultant-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/assets/ui-ux-consultant/SKILL.md +844 -0
  2. package/assets/ui-ux-consultant/references/accessibility.md +175 -0
  3. package/assets/ui-ux-consultant/references/alt-libraries.md +90 -0
  4. package/assets/ui-ux-consultant/references/animations.md +448 -0
  5. package/assets/ui-ux-consultant/references/catalog/colors.md +91 -0
  6. package/assets/ui-ux-consultant/references/catalog/fonts.md +363 -0
  7. package/assets/ui-ux-consultant/references/catalog/products.md +340 -0
  8. package/assets/ui-ux-consultant/references/catalog/styles.md +165 -0
  9. package/assets/ui-ux-consultant/references/components.md +1116 -0
  10. package/assets/ui-ux-consultant/references/patterns.md +600 -0
  11. package/assets/ui-ux-consultant/references/performance.md +198 -0
  12. package/assets/ui-ux-consultant/references/stacks/astro.md +382 -0
  13. package/assets/ui-ux-consultant/references/stacks/flutter.md +308 -0
  14. package/assets/ui-ux-consultant/references/stacks/html-tailwind.md +415 -0
  15. package/assets/ui-ux-consultant/references/stacks/jetpack-compose.md +333 -0
  16. package/assets/ui-ux-consultant/references/stacks/laravel.md +521 -0
  17. package/assets/ui-ux-consultant/references/stacks/nextjs.md +275 -0
  18. package/assets/ui-ux-consultant/references/stacks/nuxt-ui.md +384 -0
  19. package/assets/ui-ux-consultant/references/stacks/nuxtjs.md +264 -0
  20. package/assets/ui-ux-consultant/references/stacks/react-native.md +346 -0
  21. package/assets/ui-ux-consultant/references/stacks/react.md +268 -0
  22. package/assets/ui-ux-consultant/references/stacks/shadcn.md +485 -0
  23. package/assets/ui-ux-consultant/references/stacks/svelte.md +429 -0
  24. package/assets/ui-ux-consultant/references/stacks/swiftui.md +336 -0
  25. package/assets/ui-ux-consultant/references/stacks/threejs.md +366 -0
  26. package/assets/ui-ux-consultant/references/stacks/vue.md +272 -0
  27. package/assets/ui-ux-consultant/references/theming.md +701 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +130 -0
  30. package/package.json +51 -0
@@ -0,0 +1,485 @@
1
+ # shadcn/ui — UI/UX Reference
2
+
3
+ ## When to Read
4
+ Use this file when building with shadcn/ui — the copy-paste component system built on Radix UI primitives + Tailwind CSS. Works with React, Next.js, Astro, Remix, and any Tailwind-compatible React project.
5
+
6
+ ---
7
+
8
+ ## What shadcn/ui Is (and Isn't)
9
+
10
+ **NOT a component library you install as a package.** You copy component source directly into your project (`components/ui/`). Each component is yours to read, modify, and own. The `npx shadcn` CLI adds components as files — you have full control.
11
+
12
+ **Built on:**
13
+ - **Radix UI** — headless, accessible primitives (dialogs, dropdowns, tooltips, etc.)
14
+ - **Tailwind CSS** — utility classes for all styling
15
+ - **class-variance-authority (cva)** — variant-based component styling
16
+ - **clsx + tailwind-merge** — safe class name merging via `cn()`
17
+
18
+ ---
19
+
20
+ ## Recommended Additions
21
+
22
+ | Package | Purpose | Install |
23
+ |---|---|---|
24
+ | `react-hook-form` | Form state management | `npm install react-hook-form` |
25
+ | `zod` | Schema validation | `npm install zod` |
26
+ | `@hookform/resolvers` | Connect zod to react-hook-form | `npm install @hookform/resolvers` |
27
+ | `@tanstack/react-table` | Headless data tables | `npm install @tanstack/react-table` |
28
+ | `cmdk` | Command palette | bundled with shadcn Command |
29
+ | `sonner` | Toast notifications | `npx shadcn@latest add sonner` |
30
+ | `vaul` | Drawer / bottom sheet | `npx shadcn@latest add drawer` |
31
+ | `recharts` | Charts | `npx shadcn@latest add chart` |
32
+ | `date-fns` | Date utilities | `npm install date-fns` |
33
+
34
+ ---
35
+
36
+ ## Style Recommendations
37
+
38
+ - **Default shadcn style:** Clean, professional, neutral — safe for B2B SaaS
39
+ - **New York style:** Sharper borders, tighter spacing — modern editorial feel
40
+ - **Brand customization:** Edit `--primary` CSS variable; all components update
41
+ - **Dark mode:** Built-in via `.dark` class on `<html>`; works out of the box
42
+ - **Recommended pairings:** Inter or Geist font, 4px border radius for friendly, 0px for stark
43
+
44
+ ---
45
+
46
+ ## Setup
47
+
48
+ ```bash
49
+ # Initialize shadcn in a Vite/Next.js/Astro project
50
+ npx shadcn@latest init
51
+ # Prompts: style (Default / New York), base color, CSS variables (yes)
52
+
53
+ # Add individual components
54
+ npx shadcn@latest add button
55
+ npx shadcn@latest add card dialog form table badge avatar
56
+
57
+ # Add multiple at once
58
+ npx shadcn@latest add button card dialog form input label select textarea
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Core Architecture Patterns
64
+
65
+ ### The `cn()` Helper
66
+
67
+ ```typescript
68
+ // lib/utils.ts (auto-generated by init)
69
+ import { clsx, type ClassValue } from 'clsx';
70
+ import { twMerge } from 'tailwind-merge';
71
+
72
+ export function cn(...inputs: ClassValue[]) {
73
+ return twMerge(clsx(inputs));
74
+ }
75
+
76
+ // Usage — merges classes safely, resolves Tailwind conflicts
77
+ <Button className={cn('w-full', isLoading && 'opacity-50', className)} />
78
+ ```
79
+
80
+ ### Variant-Based Styling with `cva`
81
+
82
+ ```typescript
83
+ // components/ui/button.tsx
84
+ import { cva, type VariantProps } from 'class-variance-authority';
85
+
86
+ const buttonVariants = cva(
87
+ // Base classes (always applied)
88
+ 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
89
+ {
90
+ variants: {
91
+ variant: {
92
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
93
+ outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
94
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
95
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
96
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
97
+ link: 'text-primary underline-offset-4 hover:underline',
98
+ },
99
+ size: {
100
+ default: 'h-10 px-4 py-2',
101
+ sm: 'h-9 rounded-md px-3',
102
+ lg: 'h-11 rounded-md px-8',
103
+ icon: 'h-10 w-10',
104
+ },
105
+ },
106
+ defaultVariants: { variant: 'default', size: 'default' },
107
+ }
108
+ );
109
+
110
+ interface ButtonProps
111
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
112
+ VariantProps<typeof buttonVariants> {
113
+ asChild?: boolean;
114
+ }
115
+
116
+ export function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) {
117
+ const Comp = asChild ? Slot : 'button';
118
+ return <Comp className={cn(buttonVariants({ variant, size, className }))} {...props} />;
119
+ }
120
+ ```
121
+
122
+ ### `asChild` Pattern
123
+
124
+ ```tsx
125
+ // asChild renders the child element instead of the default element
126
+ // Use for link buttons, custom triggers, etc.
127
+ <Button asChild>
128
+ <Link href="/dashboard">Go to Dashboard</Link>
129
+ </Button>
130
+
131
+ <DialogTrigger asChild>
132
+ <Button variant="outline">Open Dialog</Button>
133
+ </DialogTrigger>
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Top UX Patterns with Code
139
+
140
+ ### 1. Form with Validation (react-hook-form + zod)
141
+
142
+ ```tsx
143
+ import { useForm } from 'react-hook-form';
144
+ import { zodResolver } from '@hookform/resolvers/zod';
145
+ import { z } from 'zod';
146
+ import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
147
+ import { Input } from '@/components/ui/input';
148
+ import { Button } from '@/components/ui/button';
149
+
150
+ const formSchema = z.object({
151
+ email: z.string().email('Invalid email address'),
152
+ name: z.string().min(2, 'Name must be at least 2 characters'),
153
+ role: z.enum(['admin', 'user', 'viewer']),
154
+ });
155
+
156
+ type FormValues = z.infer<typeof formSchema>;
157
+
158
+ export function UserForm({ onSubmit }: { onSubmit: (data: FormValues) => Promise<void> }) {
159
+ const form = useForm<FormValues>({
160
+ resolver: zodResolver(formSchema),
161
+ defaultValues: { email: '', name: '', role: 'user' },
162
+ });
163
+
164
+ return (
165
+ <Form {...form}>
166
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
167
+ <FormField
168
+ control={form.control}
169
+ name="email"
170
+ render={({ field }) => (
171
+ <FormItem>
172
+ <FormLabel>Email</FormLabel>
173
+ <FormControl>
174
+ <Input type="email" placeholder="you@example.com" {...field} />
175
+ </FormControl>
176
+ <FormDescription>We'll never share your email.</FormDescription>
177
+ <FormMessage /> {/* Auto-shows zod error messages */}
178
+ </FormItem>
179
+ )}
180
+ />
181
+ <Button type="submit" disabled={form.formState.isSubmitting}>
182
+ {form.formState.isSubmitting ? 'Saving...' : 'Save'}
183
+ </Button>
184
+ </form>
185
+ </Form>
186
+ );
187
+ }
188
+ ```
189
+
190
+ ### 2. Dialog / Modal
191
+
192
+ ```tsx
193
+ import {
194
+ Dialog, DialogContent, DialogDescription,
195
+ DialogFooter, DialogHeader, DialogTitle, DialogTrigger,
196
+ } from '@/components/ui/dialog';
197
+
198
+ export function DeleteConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
199
+ const [open, setOpen] = React.useState(false);
200
+
201
+ return (
202
+ <Dialog open={open} onOpenChange={setOpen}>
203
+ <DialogTrigger asChild>
204
+ <Button variant="destructive">Delete</Button>
205
+ </DialogTrigger>
206
+ <DialogContent className="sm:max-w-md">
207
+ <DialogHeader>
208
+ <DialogTitle>Are you absolutely sure?</DialogTitle>
209
+ <DialogDescription>
210
+ This action cannot be undone. This will permanently delete your data.
211
+ </DialogDescription>
212
+ </DialogHeader>
213
+ <DialogFooter>
214
+ <Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
215
+ <Button variant="destructive" onClick={() => { onConfirm(); setOpen(false); }}>
216
+ Delete
217
+ </Button>
218
+ </DialogFooter>
219
+ </DialogContent>
220
+ </Dialog>
221
+ );
222
+ }
223
+ ```
224
+
225
+ ### 3. Data Table (TanStack Table)
226
+
227
+ ```tsx
228
+ import {
229
+ useReactTable, getCoreRowModel, getSortedRowModel,
230
+ getPaginationRowModel, getFilteredRowModel,
231
+ type ColumnDef, type SortingState,
232
+ } from '@tanstack/react-table';
233
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
234
+
235
+ interface User { id: string; name: string; email: string; role: string; }
236
+
237
+ const columns: ColumnDef<User>[] = [
238
+ { accessorKey: 'name', header: 'Name' },
239
+ { accessorKey: 'email', header: 'Email' },
240
+ {
241
+ accessorKey: 'role',
242
+ header: 'Role',
243
+ cell: ({ row }) => <Badge variant="outline">{row.getValue('role')}</Badge>,
244
+ },
245
+ {
246
+ id: 'actions',
247
+ cell: ({ row }) => <UserActions user={row.original} />,
248
+ },
249
+ ];
250
+
251
+ export function UsersTable({ data }: { data: User[] }) {
252
+ const [sorting, setSorting] = React.useState<SortingState>([]);
253
+ const [globalFilter, setGlobalFilter] = React.useState('');
254
+
255
+ const table = useReactTable({
256
+ data,
257
+ columns,
258
+ state: { sorting, globalFilter },
259
+ onSortingChange: setSorting,
260
+ onGlobalFilterChange: setGlobalFilter,
261
+ getCoreRowModel: getCoreRowModel(),
262
+ getSortedRowModel: getSortedRowModel(),
263
+ getPaginationRowModel: getPaginationRowModel(),
264
+ getFilteredRowModel: getFilteredRowModel(),
265
+ });
266
+
267
+ return (
268
+ <div className="space-y-4">
269
+ <Input placeholder="Search..." value={globalFilter} onChange={e => setGlobalFilter(e.target.value)} />
270
+ <Table>
271
+ <TableHeader>
272
+ {table.getHeaderGroups().map(group => (
273
+ <TableRow key={group.id}>
274
+ {group.headers.map(header => (
275
+ <TableHead key={header.id}
276
+ onClick={header.column.getToggleSortingHandler()}
277
+ className="cursor-pointer">
278
+ {flexRender(header.column.columnDef.header, header.getContext())}
279
+ </TableHead>
280
+ ))}
281
+ </TableRow>
282
+ ))}
283
+ </TableHeader>
284
+ <TableBody>
285
+ {table.getRowModel().rows.map(row => (
286
+ <TableRow key={row.id}>
287
+ {row.getVisibleCells().map(cell => (
288
+ <TableCell key={cell.id}>
289
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
290
+ </TableCell>
291
+ ))}
292
+ </TableRow>
293
+ ))}
294
+ </TableBody>
295
+ </Table>
296
+ </div>
297
+ );
298
+ }
299
+ ```
300
+
301
+ ### 4. Command Palette
302
+
303
+ ```tsx
304
+ import {
305
+ CommandDialog, CommandEmpty, CommandGroup,
306
+ CommandInput, CommandItem, CommandList,
307
+ } from '@/components/ui/command';
308
+
309
+ export function CommandPalette() {
310
+ const [open, setOpen] = React.useState(false);
311
+
312
+ React.useEffect(() => {
313
+ const down = (e: KeyboardEvent) => {
314
+ if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
315
+ e.preventDefault();
316
+ setOpen(prev => !prev);
317
+ }
318
+ };
319
+ document.addEventListener('keydown', down);
320
+ return () => document.removeEventListener('keydown', down);
321
+ }, []);
322
+
323
+ return (
324
+ <CommandDialog open={open} onOpenChange={setOpen}>
325
+ <CommandInput placeholder="Type a command or search..." />
326
+ <CommandList>
327
+ <CommandEmpty>No results found.</CommandEmpty>
328
+ <CommandGroup heading="Navigation">
329
+ <CommandItem onSelect={() => { router.push('/dashboard'); setOpen(false); }}>
330
+ Dashboard
331
+ </CommandItem>
332
+ <CommandItem onSelect={() => { router.push('/settings'); setOpen(false); }}>
333
+ Settings
334
+ </CommandItem>
335
+ </CommandGroup>
336
+ </CommandList>
337
+ </CommandDialog>
338
+ );
339
+ }
340
+ ```
341
+
342
+ ### 5. Toast Notifications with Sonner
343
+
344
+ ```tsx
345
+ // In layout — add once
346
+ import { Toaster } from '@/components/ui/sonner';
347
+ <Toaster position="bottom-right" richColors />
348
+
349
+ // In any component
350
+ import { toast } from 'sonner';
351
+
352
+ // Usage
353
+ toast.success('Saved successfully');
354
+ toast.error('Something went wrong');
355
+ toast.promise(saveUser(data), {
356
+ loading: 'Saving...',
357
+ success: 'User saved!',
358
+ error: 'Failed to save',
359
+ });
360
+ ```
361
+
362
+ ### 6. Dropdown Menu
363
+
364
+ ```tsx
365
+ import {
366
+ DropdownMenu, DropdownMenuContent, DropdownMenuItem,
367
+ DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger,
368
+ } from '@/components/ui/dropdown-menu';
369
+
370
+ <DropdownMenu>
371
+ <DropdownMenuTrigger asChild>
372
+ <Button variant="ghost" size="icon"><MoreHorizontal /></Button>
373
+ </DropdownMenuTrigger>
374
+ <DropdownMenuContent align="end">
375
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
376
+ <DropdownMenuSeparator />
377
+ <DropdownMenuItem onClick={() => navigator.clipboard.writeText(id)}>
378
+ Copy ID
379
+ </DropdownMenuItem>
380
+ <DropdownMenuItem onClick={() => onEdit(item)}>Edit</DropdownMenuItem>
381
+ <DropdownMenuSeparator />
382
+ <DropdownMenuItem className="text-destructive" onClick={() => onDelete(item)}>
383
+ Delete
384
+ </DropdownMenuItem>
385
+ </DropdownMenuContent>
386
+ </DropdownMenu>
387
+ ```
388
+
389
+ ---
390
+
391
+ ## CSS Variables Theming
392
+
393
+ ```css
394
+ /* globals.css — generated by shadcn init */
395
+ @layer base {
396
+ :root {
397
+ --background: 0 0% 100%;
398
+ --foreground: 222.2 84% 4.9%;
399
+ --card: 0 0% 100%;
400
+ --card-foreground: 222.2 84% 4.9%;
401
+ --primary: 222.2 47.4% 11.2%; /* Change this for brand color */
402
+ --primary-foreground: 210 40% 98%;
403
+ --secondary: 210 40% 96.1%;
404
+ --muted: 210 40% 96.1%;
405
+ --muted-foreground: 215.4 16.3% 46.9%;
406
+ --border: 214.3 31.8% 91.4%;
407
+ --ring: 222.2 84% 4.9%;
408
+ --radius: 0.5rem; /* Change for border-radius style */
409
+ }
410
+ .dark {
411
+ --background: 222.2 84% 4.9%;
412
+ --foreground: 210 40% 98%;
413
+ --primary: 210 40% 98%;
414
+ /* ... all tokens redefined for dark */
415
+ }
416
+ }
417
+ ```
418
+
419
+ **Brand customization:** change `--primary` HSL values. All buttons, links, focus rings update automatically. No component edits needed.
420
+
421
+ ---
422
+
423
+ ## Best Practices by Category
424
+
425
+ ### Component Modification
426
+ - Edit component files directly — they live in `components/ui/`, not in `node_modules`
427
+ - Use `cn()` for all conditional class merging to avoid Tailwind conflicts
428
+ - Extend with `cva` variants rather than adding one-off className strings
429
+ - Keep customizations in the component file, not via external CSS overrides
430
+
431
+ ### Form Patterns
432
+ - Always pair shadcn Form with react-hook-form + zod for type-safe validation
433
+ - Use `FormMessage` — it auto-displays the zod error for the field
434
+ - Use `FormDescription` for helper text below inputs
435
+ - `defaultValues` in `useForm` prevents uncontrolled→controlled warnings
436
+
437
+ ### Accessibility
438
+ - Radix UI primitives handle focus trapping, keyboard navigation, ARIA automatically
439
+ - Never remove `role` or `aria-*` attributes from generated components
440
+ - Custom triggers should use `asChild` to preserve semantics
441
+ - Test with keyboard only — tab, enter, escape, arrow keys must work
442
+
443
+ ### Dark Mode
444
+ - shadcn ships with dark mode via `.dark` class on `<html>`
445
+ - Next.js: use `next-themes` (`npm install next-themes`) for system preference and toggle
446
+ - All CSS variables have dark counterparts — no component changes needed
447
+
448
+ ---
449
+
450
+ ## Common Anti-Patterns
451
+
452
+ 1. **Installing shadcn as an npm package** — there is no `npm install shadcn`. It's copy-paste by design. Components live in your codebase, not node_modules.
453
+
454
+ 2. **Not using `cn()` for conditional classes** — `className={`base ${condition ? 'a' : 'b'}`}` causes Tailwind conflicts. Always use `cn()` for merging.
455
+
456
+ 3. **Styling Radix components with external CSS** — use Tailwind classes directly in the component file. External CSS specificity battles are painful.
457
+
458
+ 4. **Adding components manually without `npx shadcn add`** — the CLI wires up imports, dependencies, and ensures correct file paths. Manual copying often misses peer deps.
459
+
460
+ 5. **Using `!important` to override** — edit the component file instead. You own it.
461
+
462
+ 6. **Wrapping every primitive in a div** — Radix's `asChild` prop renders the child element directly. Use it instead of wrapper divs.
463
+
464
+ 7. **Passing className without `cn()`** — `<Button className="w-full">` works, but `<Button className={cn('w-full', variant === 'danger' && 'bg-red-500')}>` is safer.
465
+
466
+ 8. **Not upgrading component files after `npx shadcn@latest add`** — running add again on an existing component regenerates it. Back up local modifications first.
467
+
468
+ 9. **Using `<select>` instead of shadcn Select** — native select is unstyled and inaccessible on some platforms. Use the Radix-backed Select component.
469
+
470
+ 10. **Skipping `<DialogDescription>`** — Radix Dialog warns (and NVDA/JAWS users miss context) without it. Always include for screen reader support.
471
+
472
+ ---
473
+
474
+ ## Performance Checklist
475
+
476
+ - [ ] Tree-shaking: only imported components are bundled (no barrel `index.ts` needed)
477
+ - [ ] `asChild` to avoid unnecessary DOM wrappers
478
+ - [ ] Lazy-load Dialog, Sheet, Drawer content: `React.lazy(() => import('./HeavyDialog'))`
479
+ - [ ] TanStack Table pagination — render only visible rows, not all data
480
+ - [ ] `sonner` toasts are lazy-rendered, not always in DOM
481
+ - [ ] Radix portals render outside the DOM tree — no overflow:hidden issues
482
+ - [ ] `cmdk` Command palette uses virtual scrolling for large lists
483
+ - [ ] Avoid re-creating `columns` array on every render — define outside component or `useMemo`
484
+ - [ ] Dark mode via CSS variables — zero JavaScript color computation at runtime
485
+ - [ ] `cn()` uses `twMerge` which is ~2KB — import only where needed