# UI Components: [Project Name]
**Framework**: React 19
**Component Library**: shadcn/ui (Radix UI primitives)
**Styling**: Tailwind v4
**Forms**: React Hook Form + Zod
**State**: TanStack Query (server) + Zustand (client)
**Last Updated**: [Date]
---
## Overview
This document outlines the component hierarchy, reusable components, forms, and UI patterns.
**Component Philosophy**:
- **Composition over configuration** - Build complex UIs from simple parts
- **Accessibility first** - All components keyboard navigable, ARIA compliant
- **shadcn/ui ownership** - Components copied to codebase, not npm dependency
- **Tailwind utility classes** - Minimal custom CSS
- **Dark mode support** - All components adapt to theme
---
## Component Hierarchy
```
App
├── Providers
│ ├── ClerkProvider (auth)
│ ├── ThemeProvider (dark/light mode)
│ └── QueryClientProvider (TanStack Query)
│
├── Layout
│ ├── Header
│ │ ├── Logo
│ │ ├── Navigation
│ │ └── UserMenu
│ ├── Main (page content)
│ └── Footer (optional)
│
└── Pages (routes)
├── HomePage
│ ├── HeroSection
│ ├── FeaturesSection
│ └── CTASection
│
├── DashboardPage
│ ├── Sidebar
│ │ └── NavLinks
│ └── DashboardContent
│ ├── StatsCards
│ └── [ResourceList]
│
├── [Resource]Page
│ ├── [Resource]List
│ │ ├── [Resource]Card (for each item)
│ │ └── Pagination
│ └── [Resource]CreateDialog
│
└── [Resource]DetailPage
├── [Resource]Header
├── [Resource]Info
└── [Resource]Actions
```
---
## Core Layout Components
### `App.tsx`
**Purpose**: Root component with providers
**Structure**:
```tsx
import { ClerkProvider } from '@clerk/clerk-react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ThemeProvider } from '@/components/theme-provider'
import { Router } from '@/router'
const queryClient = new QueryClient()
export function App() {
return (
)
}
```
---
### `Layout.tsx`
**Purpose**: Common layout wrapper for authenticated pages
**Props**: None (uses `Outlet` from react-router)
**Structure**:
```tsx
export function Layout() {
return (
)
}
```
**Files**: `src/components/layout/Layout.tsx`, `Header.tsx`, `Footer.tsx`
---
### `Header.tsx`
**Purpose**: Top navigation bar
**Features**:
- Logo/brand
- Navigation links
- User menu (authenticated) or Sign In button (unauthenticated)
- Theme toggle (dark/light mode)
**Structure**:
```tsx
export function Header() {
return (
)
}
```
**Files**: `src/components/layout/Header.tsx`
---
## shadcn/ui Components Used
### Installed Components
Run these to add components:
```bash
pnpm dlx shadcn@latest add button
pnpm dlx shadcn@latest add dialog
pnpm dlx shadcn@latest add dropdown-menu
pnpm dlx shadcn@latest add form
pnpm dlx shadcn@latest add input
pnpm dlx shadcn@latest add label
pnpm dlx shadcn@latest add select
pnpm dlx shadcn@latest add textarea
pnpm dlx shadcn@latest add card
pnpm dlx shadcn@latest add badge
pnpm dlx shadcn@latest add avatar
pnpm dlx shadcn@latest add skeleton
pnpm dlx shadcn@latest add toast
```
**Location**: `src/components/ui/`
**Ownership**: These are now part of our codebase, modify as needed
---
## Custom Components
### `[Resource]List.tsx`
**Purpose**: Display list of [resources] with loading/error states
**Props**:
```typescript
interface [Resource]ListProps {
// No props - uses TanStack Query internally
}
```
**Structure**:
```tsx
export function [Resource]List() {
const { data, isLoading, error } = useQuery({
queryKey: ['[resources]'],
queryFn: fetch[Resources]
})
if (isLoading) return <[Resource]ListSkeleton />
if (error) return
return (
{data.map(item => (
<[Resource]Card key={item.id} item={item} />
))}
)
}
```
**Files**: `src/components/[resource]/[Resource]List.tsx`
---
### `[Resource]Card.tsx`
**Purpose**: Display single [resource] item
**Props**:
```typescript
interface [Resource]CardProps {
item: [Resource]
onEdit?: (id: number) => void
onDelete?: (id: number) => void
}
```
**Structure**:
```tsx
export function [Resource]Card({ item, onEdit, onDelete }: [Resource]CardProps) {
return (
{item.title}
{item.description}
)
}
```
**Files**: `src/components/[resource]/[Resource]Card.tsx`
---
### `[Resource]Form.tsx`
**Purpose**: Create or edit [resource]
**Props**:
```typescript
interface [Resource]FormProps {
item?: [Resource] // undefined for create, populated for edit
onSuccess?: () => void
}
```
**Structure** (using React Hook Form + Zod):
```tsx
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { [resource]Schema } from '@/lib/schemas'
export function [Resource]Form({ item, onSuccess }: [Resource]FormProps) {
const form = useForm({
resolver: zodResolver([resource]Schema),
defaultValues: item || {
field1: '',
field2: ''
}
})
const mutation = useMutation({
mutationFn: item ? update[Resource] : create[Resource],
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['[resources]'] })
onSuccess?.()
}
})
return (
)
}
```
**Files**: `src/components/[resource]/[Resource]Form.tsx`
---
### `[Resource]CreateDialog.tsx`
**Purpose**: Modal dialog for creating new [resource]
**Structure**:
```tsx
export function [Resource]CreateDialog() {
const [open, setOpen] = useState(false)
return (
)
}
```
**Files**: `src/components/[resource]/[Resource]CreateDialog.tsx`
---
## Loading and Error States
### `[Resource]ListSkeleton.tsx`
**Purpose**: Loading state for [resource] list
**Structure**:
```tsx
export function [Resource]ListSkeleton() {
return (
{[1, 2, 3].map(i => (
))}
)
}
```
---
### `ErrorMessage.tsx`
**Purpose**: Reusable error display
**Props**:
```typescript
interface ErrorMessageProps {
error: Error
retry?: () => void
}
```
**Structure**:
```tsx
export function ErrorMessage({ error, retry }: ErrorMessageProps) {
return (
Error
{error.message}
{retry && (
)}
)
}
```
---
## State Management Patterns
### Server State (TanStack Query)
**Use for**: Data from API (users, tasks, analytics, etc)
**Example**:
```tsx
// In component
const { data, isLoading, error } = useQuery({
queryKey: ['tasks'],
queryFn: () => api.get('/api/tasks').then(res => res.data)
})
// Mutation
const mutation = useMutation({
mutationFn: (newTask) => api.post('/api/tasks', newTask),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] })
}
})
```
**Files**: Queries/mutations defined in `src/lib/api.ts` or component files
---
### Client State (Zustand)
**Use for**: UI state, preferences, filters
**Example Store**:
```tsx
// src/stores/ui-store.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface UIStore {
sidebarOpen: boolean
setSidebarOpen: (open: boolean) => void
theme: 'light' | 'dark' | 'system'
setTheme: (theme: 'light' | 'dark' | 'system') => void
}
export const useUIStore = create()(
persist(
(set) => ({
sidebarOpen: true,
setSidebarOpen: (open) => set({ sidebarOpen: open }),
theme: 'system',
setTheme: (theme) => set({ theme })
}),
{ name: 'ui-store' }
)
)
```
**Usage**:
```tsx
const { sidebarOpen, setSidebarOpen } = useUIStore()
```
---
## Theme System
### ThemeProvider
**Purpose**: Manage dark/light/system theme
**Structure**:
```tsx
// src/components/theme-provider.tsx
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return (
{children}
)
}
export function useTheme() {
return useNextTheme()
}
```
**Files**: `src/components/theme-provider.tsx`
---
### ThemeToggle
**Purpose**: Toggle between light/dark/system themes
**Structure**:
```tsx
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
return (
setTheme('light')}>Light
setTheme('dark')}>Dark
setTheme('system')}>System
)
}
```
---
## Form Patterns
All forms use **React Hook Form + Zod** for validation.
**Schema Definition** (shared client + server):
```typescript
// src/lib/schemas.ts
import { z } from 'zod'
export const taskSchema = z.object({
title: z.string().min(1, 'Title required').max(100),
description: z.string().optional(),
dueDate: z.string().optional(),
priority: z.enum(['low', 'medium', 'high']).default('medium')
})
export type TaskFormData = z.infer
```
**Form Component**:
```tsx
const form = useForm({
resolver: zodResolver(taskSchema),
defaultValues: { ... }
})
```
---
## Responsive Design
**Breakpoints** (Tailwind defaults):
- `sm`: 640px
- `md`: 768px
- `lg`: 1024px
- `xl`: 1280px
- `2xl`: 1536px
**Patterns**:
```tsx
// Stack on mobile, grid on desktop
// Hide on mobile, show on desktop
// Different padding on mobile vs desktop
```
---
## Accessibility
**Requirements**:
- All interactive elements keyboard navigable
- Focus states visible (use `ring` classes)
- Images have alt text
- Forms have associated labels
- Color contrast meets WCAG AA
- Screen reader friendly (ARIA labels)
**shadcn/ui benefits**: All components built with Radix UI (accessible by default)
---
## File Structure
```
src/
├── components/
│ ├── ui/ # shadcn/ui components
│ │ ├── button.tsx
│ │ ├── dialog.tsx
│ │ ├── form.tsx
│ │ └── ...
│ ├── layout/ # Layout components
│ │ ├── Header.tsx
│ │ ├── Footer.tsx
│ │ └── Layout.tsx
│ ├── [resource]/ # Feature-specific components
│ │ ├── [Resource]List.tsx
│ │ ├── [Resource]Card.tsx
│ │ ├── [Resource]Form.tsx
│ │ └── [Resource]CreateDialog.tsx
│ ├── theme-provider.tsx
│ └── error-message.tsx
├── lib/
│ ├── schemas.ts # Zod schemas
│ ├── api.ts # API client
│ └── utils.ts # cn() helper
├── stores/
│ └── ui-store.ts # Zustand stores
└── pages/
├── HomePage.tsx
├── DashboardPage.tsx
└── ...
```
---
## Design Tokens
See `src/index.css` for theme configuration.
**Colors** (semantic):
- `background` / `foreground`
- `primary` / `primary-foreground`
- `secondary` / `secondary-foreground`
- `destructive` / `destructive-foreground`
- `muted` / `muted-foreground`
- `accent` / `accent-foreground`
- `border`
- `ring`
**Usage**: These adapt to light/dark mode automatically.
---
## Future Components
Components to build:
- [ ] `[Component]` - [Description]
- [ ] `[Component]` - [Description]
---
## Revision History
**v1.0** ([Date]): Initial component structure
**v1.1** ([Date]): [Changes made]