Scan the project's component library for existing patterns — packages/ui/src/components/ in monorepos, components/ in standalone projects (if exists)
Scan the project's theme variables — packages/ui/src/globals.css in monorepos, or in standalone (if exists)
src/globals.css
app/globals.css
Critical Rules (All Sub-Commands)
1. Theme Variables Only
Never use raw Tailwind colors. Always use theme variables from globals.css:
// NEVER - raw Tailwind colors
className = 'text-green-500 bg-green-50 border-green-500/50'// ALWAYS - theme variables
className = 'text-success bg-success-muted border-success/20'
Common theme colors (define in your globals.css):
primary, secondary, muted, accent
success, warning, destructive, info (each with -foreground and -muted variants)
border, input, ring, background, foreground
card, popover (each with -foreground)
Color format: shadcn/ui uses OKLCH (newer projects) or HSL (older projects). Either works — the critical rule is semantic variable names, not the underlying format. Tailwind v4 projects use @theme in CSS instead of tailwind.config.js.
Before creating anything new, check existing components (packages/ui/src/components/ in monorepos, components/ in standalone). Common pattern types:
Sheets: Action sheets (form submissions with footer buttons) vs Settings sheets (configuration with inline actions).
Forms: Complete field components (TextField, SelectField, etc.) with label, description, and validation built in.
Layout: Section + SectionHeader for grouping, PageHeader for page titles, KPICard for metrics, EmptyState for placeholders, DangerZone for destructive actions.
Build complete field components in your shared UI package (composing shadcn Field primitives internally). App code consumes the complete fields — never assembles primitives:
shadcn provides composable Field, FieldLabel, FieldDescription, and FieldError primitives. Use these inside your shared library when building complete fields — don't expose them to app code.
4. React Hook Form + Zod for All Forms
Never use useState per form field:
// ALWAYS - React Hook Form + Zodconst schema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email')
})
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: { name: '', email: '' }
})
// NEVER - useState per fieldconst [name, setName] = useState('')
Use shared schemas from your contracts package when available. Local schemas only for purely UI validations.
Watch values reactively with useWatch when UI needs to react to form field changes.
Zod v4 note: If using Zod v4+, use error instead of message in refinements, .extend() instead of .merge(), and z.file() for file uploads.
5. Ask Before Coding
All sub-commands (except review) must: analyze code, present plan, wait for approval.
6. Icon Sizing in Buttons
Use size-4 class. Button handles gap spacing, so mr-2 is unnecessary:
Content components should use fragments, not wrapper divs - the shell provides space-y-6.
8. Theme Variable Parity Across Apps
When sharing components across multiple apps, verify all consuming apps define the required CSS variables.
9. PageHeader for Detail Pages
Detail pages need full treatment: icon + title + description + actions. Primary CTA uses default variant (solid), secondary uses variant="outline".
10. Section Pattern for Content Groups
Use Section and SectionHeader for semantic grouping instead of raw divs with manual styling.
11. DataTable Column Memoization
When DataTable columns reference callbacks, you MUST wrap callbacks in useCallback and columns in useMemo. Without this, columns recreate every render causing performance issues.
React Compiler: If the project uses React Compiler, manual memoization is handled automatically and this rule can be relaxed.
Table vs DataTable: Use DataTable for data management (sorting, filtering, pagination). Use raw Table for static summary displays (max ~5 items).
12. Accessibility Defaults
All form fields must connect errors to inputs via aria-invalid and aria-describedby. On submit failure, call form.setFocus() on the first errored field. Dynamic error messages need aria-live="polite". Custom interactive components must support keyboard navigation. Theme variable colors must meet WCAG contrast requirements.
13. shadcn CLI for All Primitives
Never recreate shadcn components manually. Always install via CLI:
React Server Component classification ('use client' rules)
Container queries for shared components
/react-ui review
Quality audit without code changes. Lists issues and suggests which sub-commands to run.
When to Use
Before committing UI changes
Periodic quality check on a feature area
After importing or inheriting components from another project
Review Checklist
THEME COMPLIANCE
[ ] No raw Tailwind colors
[ ] Uses theme variables
[ ] Dark mode compatible
PATTERN USAGE
[ ] Sheets use project patterns
[ ] Forms use React Hook Form + Zod
[ ] Errors use proper components
[ ] Fields use complete field components
SOLID PRINCIPLES
[ ] Single Responsibility per component
[ ] Components < 150 lines render
[ ] Focused props interface
EXTRACTION CANDIDATES
[ ] Types for shared contracts
[ ] Components reusable across apps
[ ] Schemas that could be shared
ACCESSIBILITY
[ ] Form errors use aria-invalid + aria-describedby
[ ] Submit failure focuses first error field
[ ] Dynamic messages use aria-live="polite"
[ ] Custom components support keyboard navigation
REACT BEST PRACTICES
[ ] No useEffect for data fetching
[ ] Proper key props on lists
[ ] Memoization where appropriate
[ ] Skeleton returned instead of null
Must Flag (Critical Violations)
Pattern Found
Should Be
Severity
Assembling field primitives manually
Complete field component
Critical
Raw error alert markup
Form error component
Critical
Mutation onSuccess without toast
Add toast notification
Critical
Mutation onError without inline error display
form.setError or setState
Critical
Raw Tailwind color + opacity
Theme variable
Critical
Raw div danger zone markup
DangerZone component
Warning
DataTable columns with callbacks, no useMemo
Wrap in useMemo
Warning
Form field missing aria-invalid/aria-describedby
Connect error state to input
Warning
Missing 'use client' on component using hooks
Add directive
Critical
Unnecessary fragment <><Single /></>
Return element directly
Info
Checklist
Before completing any sub-command:
No raw Tailwind colors remain
All sheets use project sheet patterns
All forms use React Hook Form + Zod
Types are in shared contracts if shared
Components are in shared UI package if reusable
Skeletons exist for new shared components (if applicable)
Exports added to package index.ts files (if exists)
Imports updated in consuming files
Related Skills
/nextjs-data - SSR hydration, React Query, data fetching patterns. Use for page creation, query layers, and server/client data flow.
/js-monorepo - Monorepo infrastructure (pnpm, Turborepo, shared packages). Use when setting up the repo or adding shared packages.