Refactor useAuth hook, settings components, and docs for formatting and readability improvements

- Consolidated multi-line arguments into single lines where appropriate in `useAuth`.
- Improved spacing and readability in data processing across components (`ProfileSettingsForm`, `PasswordChangeForm`, `SessionCard`).
- Applied consistent table and markdown formatting in design system docs (e.g., `README.md`, `08-ai-guidelines.md`, `00-quick-start.md`).
- Updated code snippets to ensure adherence to Prettier rules and streamlined JSX structures.
This commit is contained in:
2025-11-10 11:03:45 +01:00
parent 464a6140c4
commit 96df7edf88
208 changed files with 4056 additions and 4556 deletions

View File

@@ -66,7 +66,7 @@ import {
CardTitle,
CardDescription,
CardContent,
CardFooter
CardFooter,
} from '@/components/ui/card';
<Card>
@@ -80,7 +80,7 @@ import {
<CardFooter>
<Button>Save</Button>
</CardFooter>
</Card>
</Card>;
```
**[See card examples](/dev/components#card)**
@@ -95,18 +95,9 @@ import { Input } from '@/components/ui/input';
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="you@example.com"
{...register('email')}
/>
{errors.email && (
<p className="text-sm text-destructive">
{errors.email.message}
</p>
)}
</div>
<Input id="email" type="email" placeholder="you@example.com" {...register('email')} />
{errors.email && <p className="text-sm text-destructive">{errors.email.message}</p>}
</div>;
```
**[See form patterns](./06-forms.md)** | **[Form examples](/dev/forms)**
@@ -123,7 +114,7 @@ import {
DialogTitle,
DialogDescription,
DialogFooter,
DialogTrigger
DialogTrigger,
} from '@/components/ui/dialog';
<Dialog>
@@ -133,16 +124,14 @@ import {
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm Action</DialogTitle>
<DialogDescription>
Are you sure you want to proceed?
</DialogDescription>
<DialogDescription>Are you sure you want to proceed?</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button>Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Dialog>;
```
**[See dialog examples](/dev/components#dialog)**
@@ -197,7 +186,7 @@ import { AlertCircle } from 'lucide-react';
```tsx
// Responsive card grid
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map(item => (
{items.map((item) => (
<Card key={item.id}>
<CardHeader>
<CardTitle>{item.title}</CardTitle>
@@ -218,9 +207,7 @@ import { AlertCircle } from 'lucide-react';
<CardTitle>Login</CardTitle>
</CardHeader>
<CardContent>
<form className="space-y-4">
{/* Form fields */}
</form>
<form className="space-y-4">{/* Form fields */}</form>
</CardContent>
</Card>
</div>
@@ -247,6 +234,7 @@ import { AlertCircle } from 'lucide-react';
```
**Available tokens:**
- `primary` - Main brand color, CTAs
- `destructive` - Errors, delete actions
- `muted` - Disabled states, subtle backgrounds
@@ -276,6 +264,7 @@ import { AlertCircle } from 'lucide-react';
```
**Common spacing values:**
- `2` (8px) - Tight spacing
- `4` (16px) - Standard spacing
- `6` (24px) - Section spacing
@@ -326,6 +315,7 @@ import { AlertCircle } from 'lucide-react';
```
**Breakpoints:**
- `sm:` 640px+
- `md:` 768px+
- `lg:` 1024px+
@@ -370,11 +360,9 @@ import { AlertCircle } from 'lucide-react';
```tsx
import { Skeleton } from '@/components/ui/skeleton';
{isLoading ? (
<Skeleton className="h-12 w-full" />
) : (
<div>{content}</div>
)}
{
isLoading ? <Skeleton className="h-12 w-full" /> : <div>{content}</div>;
}
```
### Dropdown Menu
@@ -384,7 +372,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
<DropdownMenu>
@@ -395,7 +383,7 @@ import {
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</DropdownMenu>;
```
### Badge/Tag
@@ -415,17 +403,20 @@ import { Badge } from '@/components/ui/badge';
You now know enough to build most interfaces! For deeper knowledge:
### Learn More
- **Components**: [Complete component guide](./02-components.md)
- **Layouts**: [Layout patterns](./03-layouts.md)
- **Forms**: [Form patterns & validation](./06-forms.md)
- **Custom Components**: [Component creation guide](./05-component-creation.md)
### Interactive Examples
- **[Component Showcase](/dev/components)** - All components with code
- **[Layout Examples](/dev/layouts)** - Before/after comparisons
- **[Form Examples](/dev/forms)** - Complete form implementations
### Reference
- **[Quick Reference Tables](./99-reference.md)** - Bookmark this for lookups
- **[Foundations](./01-foundations.md)** - Complete color/spacing/typography guide
@@ -449,6 +440,7 @@ Remember these and you'll be 95% compliant:
You're ready to build. When you hit edge cases or need advanced patterns, refer back to the [full documentation](./README.md).
**Bookmark these:**
- [Quick Reference](./99-reference.md) - For quick lookups
- [AI Guidelines](./08-ai-guidelines.md) - If using AI assistants
- [Component Showcase](/dev/components) - For copy-paste examples

View File

@@ -43,6 +43,7 @@
### Why OKLCH?
We use **OKLCH** (Oklab LCH) color space for:
-**Perceptual uniformity** - Colors look consistent across light/dark modes
-**Better accessibility** - Predictable contrast ratios
-**Vibrant colors** - More saturated without sacrificing legibility
@@ -55,6 +56,7 @@ We use **OKLCH** (Oklab LCH) color space for:
### Semantic Color Tokens
All colors follow the **background/foreground** convention:
- `background` - The background color
- `foreground` - The text color that goes on that background
@@ -68,11 +70,12 @@ All colors follow the **background/foreground** convention:
```css
/* Light & Dark Mode */
--primary: oklch(0.6231 0.1880 259.8145) /* Blue */
--primary-foreground: oklch(1 0 0) /* White text */
--primary: oklch(0.6231 0.188 259.8145) /* Blue */ --primary-foreground: oklch(1 0 0)
/* White text */;
```
**Usage**:
```tsx
// Primary button (most common)
<Button>Save Changes</Button>
@@ -87,12 +90,14 @@ All colors follow the **background/foreground** convention:
```
**When to use**:
- ✅ Call-to-action buttons
- ✅ Primary links
- ✅ Active states in navigation
- ✅ Important badges/tags
**When NOT to use**:
- ❌ Large background areas (too intense)
- ❌ Body text (use `text-foreground`)
- ❌ Disabled states (use `muted`)
@@ -105,15 +110,14 @@ All colors follow the **background/foreground** convention:
```css
/* Light Mode */
--secondary: oklch(0.9670 0.0029 264.5419) /* Light gray-blue */
--secondary-foreground: oklch(0.1529 0 0) /* Dark text */
/* Dark Mode */
--secondary: oklch(0.2686 0 0) /* Dark gray */
--secondary-foreground: oklch(0.9823 0 0) /* Light text */
--secondary: oklch(0.967 0.0029 264.5419) /* Light gray-blue */
--secondary-foreground: oklch(0.1529 0 0) /* Dark text */ /* Dark Mode */
--secondary: oklch(0.2686 0 0) /* Dark gray */ --secondary-foreground: oklch(0.9823 0 0)
/* Light text */;
```
**Usage**:
```tsx
// Secondary button
<Button variant="secondary">Cancel</Button>
@@ -135,15 +139,12 @@ All colors follow the **background/foreground** convention:
```css
/* Light Mode */
--muted: oklch(0.9846 0.0017 247.8389)
--muted-foreground: oklch(0.4667 0.0043 264.4327)
/* Dark Mode */
--muted: oklch(0.2393 0 0)
--muted-foreground: oklch(0.6588 0.0043 264.4327)
--muted: oklch(0.9846 0.0017 247.8389) --muted-foreground: oklch(0.4667 0.0043 264.4327)
/* Dark Mode */ --muted: oklch(0.2393 0 0) --muted-foreground: oklch(0.6588 0.0043 264.4327);
```
**Usage**:
```tsx
// Disabled button
<Button disabled>Submit</Button>
@@ -165,6 +166,7 @@ All colors follow the **background/foreground** convention:
```
**Common use cases**:
- Disabled button backgrounds
- Placeholder/skeleton loaders
- TabsList backgrounds
@@ -179,15 +181,12 @@ All colors follow the **background/foreground** convention:
```css
/* Light Mode */
--accent: oklch(0.9514 0.0250 236.8242)
--accent-foreground: oklch(0.1529 0 0)
/* Dark Mode */
--accent: oklch(0.3791 0.1378 265.5222)
--accent-foreground: oklch(0.9823 0 0)
--accent: oklch(0.9514 0.025 236.8242) --accent-foreground: oklch(0.1529 0 0) /* Dark Mode */
--accent: oklch(0.3791 0.1378 265.5222) --accent-foreground: oklch(0.9823 0 0);
```
**Usage**:
```tsx
// Dropdown menu item hover
<DropdownMenu>
@@ -205,6 +204,7 @@ All colors follow the **background/foreground** convention:
```
**Common use cases**:
- Dropdown menu item hover states
- Command palette hover states
- Highlighted sections
@@ -218,11 +218,12 @@ All colors follow the **background/foreground** convention:
```css
/* Light & Dark Mode */
--destructive: oklch(0.6368 0.2078 25.3313) /* Red */
--destructive-foreground: oklch(1 0 0) /* White text */
--destructive: oklch(0.6368 0.2078 25.3313) /* Red */ --destructive-foreground: oklch(1 0 0)
/* White text */;
```
**Usage**:
```tsx
// Delete button
<Button variant="destructive">Delete Account</Button>
@@ -246,6 +247,7 @@ All colors follow the **background/foreground** convention:
```
**When to use**:
- ✅ Delete/remove actions
- ✅ Error messages
- ✅ Validation errors
@@ -259,19 +261,15 @@ All colors follow the **background/foreground** convention:
```css
/* Light Mode */
--card: oklch(1.0000 0 0) /* White */
--card-foreground: oklch(0.1529 0 0) /* Dark text */
--popover: oklch(1.0000 0 0) /* White */
--popover-foreground: oklch(0.1529 0 0) /* Dark text */
/* Dark Mode */
--card: oklch(0.2686 0 0) /* Dark gray */
--card-foreground: oklch(0.9823 0 0) /* Light text */
--popover: oklch(0.2686 0 0) /* Dark gray */
--popover-foreground: oklch(0.9823 0 0) /* Light text */
--card: oklch(1 0 0) /* White */ --card-foreground: oklch(0.1529 0 0) /* Dark text */
--popover: oklch(1 0 0) /* White */ --popover-foreground: oklch(0.1529 0 0) /* Dark text */
/* Dark Mode */ --card: oklch(0.2686 0 0) /* Dark gray */ --card-foreground: oklch(0.9823 0 0)
/* Light text */ --popover: oklch(0.2686 0 0) /* Dark gray */
--popover-foreground: oklch(0.9823 0 0) /* Light text */;
```
**Usage**:
```tsx
// Card (uses card colors by default)
<Card>
@@ -296,15 +294,12 @@ All colors follow the **background/foreground** convention:
```css
/* Light Mode */
--border: oklch(0.9276 0.0058 264.5313)
--input: oklch(0.9276 0.0058 264.5313)
/* Dark Mode */
--border: oklch(0.3715 0 0)
--input: oklch(0.3715 0 0)
--border: oklch(0.9276 0.0058 264.5313) --input: oklch(0.9276 0.0058 264.5313) /* Dark Mode */
--border: oklch(0.3715 0 0) --input: oklch(0.3715 0 0);
```
**Usage**:
```tsx
// Input border
<Input type="email" placeholder="you@example.com" />
@@ -329,10 +324,11 @@ All colors follow the **background/foreground** convention:
```css
/* Light & Dark Mode */
--ring: oklch(0.6231 0.1880 259.8145) /* Primary blue */
--ring: oklch(0.6231 0.188 259.8145) /* Primary blue */;
```
**Usage**:
```tsx
// Button with focus ring (automatic)
<Button>Click me</Button>
@@ -355,14 +351,14 @@ All colors follow the **background/foreground** convention:
**Purpose**: Data visualization with harmonious color palette
```css
--chart-1: oklch(0.6231 0.1880 259.8145) /* Blue */
--chart-2: oklch(0.5461 0.2152 262.8809) /* Purple-blue */
--chart-3: oklch(0.4882 0.2172 264.3763) /* Deep purple */
--chart-4: oklch(0.4244 0.1809 265.6377) /* Violet */
--chart-5: oklch(0.3791 0.1378 265.5222) /* Deep violet */
--chart-1: oklch(0.6231 0.188 259.8145) /* Blue */ --chart-2: oklch(0.5461 0.2152 262.8809)
/* Purple-blue */ --chart-3: oklch(0.4882 0.2172 264.3763) /* Deep purple */
--chart-4: oklch(0.4244 0.1809 265.6377) /* Violet */ --chart-5: oklch(0.3791 0.1378 265.5222)
/* Deep violet */;
```
**Usage**:
```tsx
// In chart components
const COLORS = [
@@ -436,12 +432,13 @@ What's the purpose?
### Font Families
```css
--font-sans: Geist Sans, system-ui, -apple-system, sans-serif
--font-mono: Geist Mono, ui-monospace, monospace
--font-serif: ui-serif, Georgia, serif
--font-sans:
Geist Sans, system-ui, -apple-system, sans-serif --font-mono: Geist Mono, ui-monospace,
monospace --font-serif: ui-serif, Georgia, serif;
```
**Usage**:
```tsx
// Sans serif (default)
<div className="font-sans">Body text</div>
@@ -457,21 +454,21 @@ What's the purpose?
### Type Scale
| Size | Class | rem | px | Use Case |
|------|-------|-----|----|----|
| 9xl | `text-9xl` | 8rem | 128px | Hero text (rare) |
| 8xl | `text-8xl` | 6rem | 96px | Hero text (rare) |
| 7xl | `text-7xl` | 4.5rem | 72px | Hero text (rare) |
| 6xl | `text-6xl` | 3.75rem | 60px | Hero text (rare) |
| 5xl | `text-5xl` | 3rem | 48px | Landing page H1 |
| 4xl | `text-4xl` | 2.25rem | 36px | Page H1 |
| 3xl | `text-3xl` | 1.875rem | 30px | **Page titles** |
| 2xl | `text-2xl` | 1.5rem | 24px | **Section headings** |
| xl | `text-xl` | 1.25rem | 20px | **Card titles** |
| lg | `text-lg` | 1.125rem | 18px | **Subheadings** |
| base | `text-base` | 1rem | 16px | **Body text (default)** |
| sm | `text-sm` | 0.875rem | 14px | **Secondary text, captions** |
| xs | `text-xs` | 0.75rem | 12px | **Labels, helper text** |
| Size | Class | rem | px | Use Case |
| ---- | ----------- | -------- | ----- | ---------------------------- |
| 9xl | `text-9xl` | 8rem | 128px | Hero text (rare) |
| 8xl | `text-8xl` | 6rem | 96px | Hero text (rare) |
| 7xl | `text-7xl` | 4.5rem | 72px | Hero text (rare) |
| 6xl | `text-6xl` | 3.75rem | 60px | Hero text (rare) |
| 5xl | `text-5xl` | 3rem | 48px | Landing page H1 |
| 4xl | `text-4xl` | 2.25rem | 36px | Page H1 |
| 3xl | `text-3xl` | 1.875rem | 30px | **Page titles** |
| 2xl | `text-2xl` | 1.5rem | 24px | **Section headings** |
| xl | `text-xl` | 1.25rem | 20px | **Card titles** |
| lg | `text-lg` | 1.125rem | 18px | **Subheadings** |
| base | `text-base` | 1rem | 16px | **Body text (default)** |
| sm | `text-sm` | 0.875rem | 14px | **Secondary text, captions** |
| xs | `text-xs` | 0.75rem | 12px | **Labels, helper text** |
**Bold = most commonly used**
@@ -479,13 +476,13 @@ What's the purpose?
### Font Weights
| Weight | Class | Numeric | Use Case |
|--------|-------|---------|----------|
| Bold | `font-bold` | 700 | **Headings, emphasis** |
| Semibold | `font-semibold` | 600 | **Subheadings, buttons** |
| Medium | `font-medium` | 500 | **Labels, menu items** |
| Normal | `font-normal` | 400 | **Body text (default)** |
| Light | `font-light` | 300 | De-emphasized text |
| Weight | Class | Numeric | Use Case |
| -------- | --------------- | ------- | ------------------------ |
| Bold | `font-bold` | 700 | **Headings, emphasis** |
| Semibold | `font-semibold` | 600 | **Subheadings, buttons** |
| Medium | `font-medium` | 500 | **Labels, menu items** |
| Normal | `font-normal` | 400 | **Body text (default)** |
| Light | `font-light` | 300 | De-emphasized text |
**Bold = most commonly used**
@@ -494,35 +491,37 @@ What's the purpose?
### Typography Patterns
#### Page Title
```tsx
<h1 className="text-3xl font-bold">Page Title</h1>
```
#### Section Heading
```tsx
<h2 className="text-2xl font-semibold mb-4">Section Heading</h2>
```
#### Card Title
```tsx
<CardTitle className="text-xl font-semibold">Card Title</CardTitle>
```
#### Body Text
```tsx
<p className="text-base text-foreground">
Regular paragraph text uses the default text-base size.
</p>
<p className="text-base text-foreground">Regular paragraph text uses the default text-base size.</p>
```
#### Secondary Text
```tsx
<p className="text-sm text-muted-foreground">
Helper text, timestamps, captions
</p>
<p className="text-sm text-muted-foreground">Helper text, timestamps, captions</p>
```
#### Label
```tsx
<Label htmlFor="email" className="text-sm font-medium">
Email Address
@@ -533,16 +532,17 @@ What's the purpose?
### Line Height
| Class | Value | Use Case |
|-------|-------|----------|
| `leading-none` | 1 | Headings (rare) |
| `leading-tight` | 1.25 | **Headings** |
| `leading-snug` | 1.375 | Dense text |
| `leading-normal` | 1.5 | **Body text (default)** |
| `leading-relaxed` | 1.625 | Comfortable reading |
| `leading-loose` | 2 | Very relaxed (rare) |
| Class | Value | Use Case |
| ----------------- | ----- | ----------------------- |
| `leading-none` | 1 | Headings (rare) |
| `leading-tight` | 1.25 | **Headings** |
| `leading-snug` | 1.375 | Dense text |
| `leading-normal` | 1.5 | **Body text (default)** |
| `leading-relaxed` | 1.625 | Comfortable reading |
| `leading-loose` | 2 | Very relaxed (rare) |
**Usage**:
```tsx
// Heading
<h1 className="text-3xl font-bold leading-tight">
@@ -622,23 +622,23 @@ Tailwind uses a **0.25rem (4px) base unit**:
### Spacing Tokens
| Token | rem | Pixels | Use Case |
|-------|-----|--------|----------|
| `0` | 0 | 0px | No spacing |
| `px` | - | 1px | Borders, dividers |
| `0.5` | 0.125rem | 2px | Very tight |
| `1` | 0.25rem | 4px | Icon gaps |
| `2` | 0.5rem | 8px | **Tight spacing** (label → input) |
| `3` | 0.75rem | 12px | Component padding |
| `4` | 1rem | 16px | **Standard spacing** (form fields) |
| `5` | 1.25rem | 20px | Medium spacing |
| `6` | 1.5rem | 24px | **Section spacing** (cards) |
| `8` | 2rem | 32px | **Large gaps** |
| `10` | 2.5rem | 40px | Very large gaps |
| `12` | 3rem | 48px | **Section dividers** |
| `16` | 4rem | 64px | **Page sections** |
| `20` | 5rem | 80px | Extra large |
| `24` | 6rem | 96px | Huge spacing |
| Token | rem | Pixels | Use Case |
| ----- | -------- | ------ | ---------------------------------- |
| `0` | 0 | 0px | No spacing |
| `px` | - | 1px | Borders, dividers |
| `0.5` | 0.125rem | 2px | Very tight |
| `1` | 0.25rem | 4px | Icon gaps |
| `2` | 0.5rem | 8px | **Tight spacing** (label → input) |
| `3` | 0.75rem | 12px | Component padding |
| `4` | 1rem | 16px | **Standard spacing** (form fields) |
| `5` | 1.25rem | 20px | Medium spacing |
| `6` | 1.5rem | 24px | **Section spacing** (cards) |
| `8` | 2rem | 32px | **Large gaps** |
| `10` | 2.5rem | 40px | Very large gaps |
| `12` | 3rem | 48px | **Section dividers** |
| `16` | 4rem | 64px | **Page sections** |
| `20` | 5rem | 80px | Extra large |
| `24` | 6rem | 96px | Huge spacing |
**Bold = most commonly used**
@@ -660,18 +660,18 @@ Tailwind uses a **0.25rem (4px) base unit**:
### Max Width Scale
| Class | Pixels | Use Case |
|-------|--------|----------|
| `max-w-xs` | 320px | Tiny cards |
| `max-w-sm` | 384px | Small cards |
| `max-w-md` | 448px | **Forms** |
| `max-w-lg` | 512px | **Modals** |
| `max-w-xl` | 576px | Medium content |
| `max-w-2xl` | 672px | **Article content** |
| `max-w-3xl` | 768px | Documentation |
| `max-w-4xl` | 896px | **Wide layouts** |
| `max-w-5xl` | 1024px | Extra wide |
| `max-w-6xl` | 1152px | Very wide |
| Class | Pixels | Use Case |
| ----------- | ------ | ------------------- |
| `max-w-xs` | 320px | Tiny cards |
| `max-w-sm` | 384px | Small cards |
| `max-w-md` | 448px | **Forms** |
| `max-w-lg` | 512px | **Modals** |
| `max-w-xl` | 576px | Medium content |
| `max-w-2xl` | 672px | **Article content** |
| `max-w-3xl` | 768px | Documentation |
| `max-w-4xl` | 896px | **Wide layouts** |
| `max-w-5xl` | 1024px | Extra wide |
| `max-w-6xl` | 1152px | Very wide |
| `max-w-7xl` | 1280px | **Full page width** |
**Bold = most commonly used**
@@ -729,27 +729,28 @@ Tailwind uses a **0.25rem (4px) base unit**:
Professional shadow system for depth and elevation:
```css
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05)
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10)
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10)
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10)
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25)
--shadow-xs:
0 1px 3px 0px hsl(0 0% 0% / 0.05) --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.1),
0 1px 2px -1px hsl(0 0% 0% / 0.1) --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.1),
0 1px 2px -1px hsl(0 0% 0% / 0.1) --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.1),
0 2px 4px -1px hsl(0 0% 0% / 0.1) --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.1),
0 4px 6px -1px hsl(0 0% 0% / 0.1) --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.1),
0 8px 10px -1px hsl(0 0% 0% / 0.1) --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
```
### Shadow Usage
| Elevation | Class | Use Case |
|-----------|-------|----------|
| Base | No shadow | Buttons, inline elements |
| Low | `shadow-sm` | **Cards, panels** |
| Medium | `shadow-md` | **Dropdowns, tooltips** |
| High | `shadow-lg` | **Modals, popovers** |
| Highest | `shadow-xl` | Notifications, floating elements |
| Maximum | `shadow-2xl` | Dialogs (rare) |
| Elevation | Class | Use Case |
| --------- | ------------ | -------------------------------- |
| Base | No shadow | Buttons, inline elements |
| Low | `shadow-sm` | **Cards, panels** |
| Medium | `shadow-md` | **Dropdowns, tooltips** |
| High | `shadow-lg` | **Modals, popovers** |
| Highest | `shadow-xl` | Notifications, floating elements |
| Maximum | `shadow-2xl` | Dialogs (rare) |
**Usage**:
```tsx
// Card with subtle shadow
<Card className="shadow-sm">Card content</Card>
@@ -779,26 +780,24 @@ Professional shadow system for depth and elevation:
Consistent rounded corners across the application:
```css
--radius: 0.375rem; /* 6px - base */
--radius: 0.375rem; /* 6px - base */
--radius-sm: calc(var(--radius) - 4px) /* 2px */
--radius-md: calc(var(--radius) - 2px) /* 4px */
--radius-lg: var(--radius) /* 6px */
--radius-xl: calc(var(--radius) + 4px) /* 10px */
--radius-sm: calc(var(--radius) - 4px) /* 2px */ --radius-md: calc(var(--radius) - 2px) /* 4px */
--radius-lg: var(--radius) /* 6px */ --radius-xl: calc(var(--radius) + 4px) /* 10px */;
```
### Border Radius Scale
| Token | Class | Pixels | Use Case |
|-------|-------|--------|----------|
| None | `rounded-none` | 0px | Square elements |
| Small | `rounded-sm` | 2px | **Tags, small badges** |
| Medium | `rounded-md` | 4px | **Inputs, small buttons** |
| Large | `rounded-lg` | 6px | **Cards, buttons (default)** |
| XL | `rounded-xl` | 10px | **Large cards, modals** |
| 2XL | `rounded-2xl` | 16px | Hero sections |
| 3XL | `rounded-3xl` | 24px | Very rounded |
| Full | `rounded-full` | 9999px | **Pills, avatars, icon buttons** |
| Token | Class | Pixels | Use Case |
| ------ | -------------- | ------ | -------------------------------- |
| None | `rounded-none` | 0px | Square elements |
| Small | `rounded-sm` | 2px | **Tags, small badges** |
| Medium | `rounded-md` | 4px | **Inputs, small buttons** |
| Large | `rounded-lg` | 6px | **Cards, buttons (default)** |
| XL | `rounded-xl` | 10px | **Large cards, modals** |
| 2XL | `rounded-2xl` | 16px | Hero sections |
| 3XL | `rounded-3xl` | 24px | Very rounded |
| Full | `rounded-full` | 9999px | **Pills, avatars, icon buttons** |
**Bold = most commonly used**
@@ -854,6 +853,7 @@ Consistent rounded corners across the application:
### Most Used Tokens
**Colors**:
- `bg-primary text-primary-foreground` - CTAs
- `bg-destructive text-destructive-foreground` - Delete/errors
- `bg-muted text-muted-foreground` - Disabled/subtle
@@ -862,6 +862,7 @@ Consistent rounded corners across the application:
- `border-border` - Borders
**Typography**:
- `text-3xl font-bold` - Page titles
- `text-2xl font-semibold` - Section headings
- `text-xl font-semibold` - Card titles
@@ -869,6 +870,7 @@ Consistent rounded corners across the application:
- `text-sm text-muted-foreground` - Secondary text
**Spacing**:
- `p-4` - Standard padding (16px)
- `p-6` - Card padding (24px)
- `gap-4` - Standard gap (16px)
@@ -877,6 +879,7 @@ Consistent rounded corners across the application:
- `space-y-6` - Section spacing (24px)
**Shadows & Radius**:
- `shadow-sm` - Cards
- `shadow-md` - Dropdowns
- `shadow-lg` - Modals
@@ -896,12 +899,14 @@ Consistent rounded corners across the application:
---
**Related Documentation:**
- [Quick Start](./00-quick-start.md) - Essential patterns
- [Components](./02-components.md) - shadcn/ui library
- [Spacing Philosophy](./04-spacing-philosophy.md) - Margin vs padding strategy
- [Accessibility](./07-accessibility.md) - WCAG compliance
**External Resources:**
- [OKLCH Color Picker](https://oklch.com)
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)

View File

@@ -24,6 +24,7 @@
We use **[shadcn/ui](https://ui.shadcn.com)**, a collection of accessible, customizable components built on **Radix UI primitives**.
**Key features:**
-**Accessible** - WCAG AA compliant, keyboard navigation, screen reader support
-**Customizable** - Components are copied into your project (not npm dependencies)
-**Composable** - Build complex UIs from simple primitives
@@ -41,6 +42,7 @@ npx shadcn@latest add
```
**Installed components** (in `/src/components/ui/`):
- alert, avatar, badge, button, card, checkbox, dialog
- dropdown-menu, input, label, popover, select, separator
- sheet, skeleton, table, tabs, textarea, toast
@@ -82,16 +84,17 @@ import { Button } from '@/components/ui/button';
**When to use each variant:**
| Variant | Use Case | Example |
|---------|----------|---------|
| `default` | Primary actions, CTAs | Save, Submit, Create |
| `secondary` | Secondary actions | Cancel, Back |
| `outline` | Alternative actions | View Details, Edit |
| `ghost` | Subtle actions in lists | Icon buttons in table rows |
| `link` | In-text actions | Read more, Learn more |
| `destructive` | Delete, remove actions | Delete Account, Remove |
| Variant | Use Case | Example |
| ------------- | ----------------------- | -------------------------- |
| `default` | Primary actions, CTAs | Save, Submit, Create |
| `secondary` | Secondary actions | Cancel, Back |
| `outline` | Alternative actions | View Details, Edit |
| `ghost` | Subtle actions in lists | Icon buttons in table rows |
| `link` | In-text actions | Read more, Learn more |
| `destructive` | Delete, remove actions | Delete Account, Remove |
**Accessibility**:
- Always add `aria-label` for icon-only buttons
- Use `disabled` for unavailable actions (not hidden)
- Loading state prevents double-submission
@@ -162,6 +165,7 @@ import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
```
**Pattern: User menu**:
```tsx
<DropdownMenu>
<DropdownMenuTrigger>
@@ -323,6 +327,7 @@ import { Label } from '@/components/ui/label';
```
**Input types:**
- `text` - Default text input
- `email` - Email address
- `password` - Password field
@@ -530,6 +535,7 @@ import { AlertCircle, CheckCircle, Info } from 'lucide-react';
```
**When to use:**
- ✅ Form-level errors
- ✅ Important warnings
- ✅ Success confirmations (inline)
@@ -557,14 +563,11 @@ toast.info('Processing your request...');
toast.warning('This action cannot be undone');
// Loading (with promise)
toast.promise(
saveChanges(),
{
loading: 'Saving changes...',
success: 'Changes saved!',
error: 'Failed to save changes',
}
);
toast.promise(saveChanges(), {
loading: 'Saving changes...',
success: 'Changes saved!',
error: 'Failed to save changes',
});
// Custom with action
toast('Event has been created', {
@@ -580,6 +583,7 @@ toast.dismiss();
```
**When to use:**
- ✅ Action confirmations (saved, deleted)
- ✅ Background task updates
- ✅ Temporary errors
@@ -629,12 +633,11 @@ import { Skeleton } from '@/components/ui/skeleton';
```
**Pattern: Loading states**:
```tsx
{isLoading ? (
<Skeleton className="h-48 w-full" />
) : (
<div>{content}</div>
)}
{
isLoading ? <Skeleton className="h-48 w-full" /> : <div>{content}</div>;
}
```
---
@@ -654,7 +657,7 @@ import {
DialogDescription,
DialogFooter,
DialogTrigger,
DialogClose
DialogClose,
} from '@/components/ui/dialog';
// Basic dialog
@@ -678,7 +681,7 @@ import {
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Dialog>;
// Controlled dialog
const [isOpen, setIsOpen] = useState(false);
@@ -695,10 +698,11 @@ const [isOpen, setIsOpen] = useState(false);
}}
/>
</DialogContent>
</Dialog>
</Dialog>;
```
**Accessibility:**
- Escape key closes dialog
- Focus trapped inside dialog
- Returns focus to trigger on close
@@ -916,7 +920,7 @@ import {
TableHead,
TableRow,
TableCell,
TableCaption
TableCaption,
} from '@/components/ui/table';
<Table>
@@ -945,7 +949,7 @@ import {
<TableCell className="text-right">$2,500.00</TableCell>
</TableRow>
</TableFooter>
</Table>
</Table>;
```
**For advanced tables** (sorting, filtering, pagination), use **TanStack Table** with react-hook-form.
@@ -1014,12 +1018,14 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
</TableRow>
</TableHeader>
<TableBody>
{users.map(user => (
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<Button variant="ghost" size="sm">Edit</Button>
<Button variant="ghost" size="sm">
Edit
</Button>
</TableCell>
</TableRow>
))}
@@ -1041,9 +1047,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
<DialogContent>
<DialogHeader>
<DialogTitle>Create New User</DialogTitle>
<DialogDescription>
Add a new user to the system
</DialogDescription>
<DialogDescription>Add a new user to the system</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
@@ -1113,7 +1117,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
```tsx
<Table>
<TableBody>
{users.map(user => (
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
@@ -1134,10 +1138,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
View Details
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => handleDelete(user)}
className="text-destructive"
>
<DropdownMenuItem onClick={() => handleDelete(user)} className="text-destructive">
<Trash className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
@@ -1187,6 +1188,7 @@ Need switchable panels? → Tabs
### Component Variants Quick Reference
**Button**:
- `default` - Primary action
- `secondary` - Secondary action
- `outline` - Alternative action
@@ -1195,12 +1197,14 @@ Need switchable panels? → Tabs
- `destructive` - Delete/remove
**Badge**:
- `default` - Blue (new, active)
- `secondary` - Gray (draft, inactive)
- `outline` - Bordered (pending)
- `destructive` - Red (critical, error)
**Alert**:
- `default` - Info
- `destructive` - Error
@@ -1216,12 +1220,14 @@ Need switchable panels? → Tabs
---
**Related Documentation:**
- [Quick Start](./00-quick-start.md) - Essential patterns
- [Foundations](./01-foundations.md) - Colors, typography, spacing
- [Layouts](./03-layouts.md) - Layout patterns
- [Forms](./06-forms.md) - Form validation and patterns
**External Resources:**
- [shadcn/ui Documentation](https://ui.shadcn.com)
- [Radix UI Primitives](https://www.radix-ui.com)

View File

@@ -36,16 +36,16 @@ Use this flowchart to choose between Grid and Flex:
### Quick Rules
| Scenario | Solution |
|----------|----------|
| **Equal-width columns** | Grid (`grid grid-cols-3`) |
| **Flexible item sizes** | Flex (`flex gap-4`) |
| **2D layout (rows + cols)** | Grid (`grid grid-cols-2 grid-rows-3`) |
| **1D layout (row OR col)** | Flex (`flex` or `flex flex-col`) |
| **Card grid** | Grid (`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3`) |
| **Navbar items** | Flex (`flex items-center gap-4`) |
| **Sidebar + Content** | Flex (`flex gap-6`) |
| **Form fields** | Flex column (`flex flex-col gap-4` or `space-y-4`) |
| Scenario | Solution |
| --------------------------- | ------------------------------------------------------- |
| **Equal-width columns** | Grid (`grid grid-cols-3`) |
| **Flexible item sizes** | Flex (`flex gap-4`) |
| **2D layout (rows + cols)** | Grid (`grid grid-cols-2 grid-rows-3`) |
| **1D layout (row OR col)** | Flex (`flex` or `flex flex-col`) |
| **Card grid** | Grid (`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3`) |
| **Navbar items** | Flex (`flex items-center gap-4`) |
| **Sidebar + Content** | Flex (`flex gap-6`) |
| **Form fields** | Flex column (`flex flex-col gap-4` or `space-y-4`) |
---
@@ -68,15 +68,14 @@ These 5 patterns cover 80% of all layout needs. Master these first.
<CardHeader>
<CardTitle>Section Title</CardTitle>
</CardHeader>
<CardContent>
Page content goes here
</CardContent>
<CardContent>Page content goes here</CardContent>
</Card>
</div>
</div>
```
**Key Features:**
- `container` - Responsive container with max-width
- `mx-auto` - Center horizontally
- `px-4` - Horizontal padding (mobile-friendly)
@@ -85,6 +84,7 @@ These 5 patterns cover 80% of all layout needs. Master these first.
- `space-y-6` - Vertical spacing between children
**When to use:**
- Blog posts
- Documentation pages
- Settings pages
@@ -103,7 +103,7 @@ These 5 patterns cover 80% of all layout needs. Master these first.
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map(item => (
{items.map((item) => (
<Card key={item.id}>
<CardHeader>
<CardTitle>{item.title}</CardTitle>
@@ -119,11 +119,13 @@ These 5 patterns cover 80% of all layout needs. Master these first.
```
**Responsive behavior:**
- **Mobile** (`< 768px`): 1 column
- **Tablet** (`≥ 768px`): 2 columns
- **Desktop** (`≥ 1024px`): 3 columns
**Key Features:**
- `grid` - Use CSS Grid
- `grid-cols-1` - Default: 1 column (mobile-first)
- `md:grid-cols-2` - 2 columns on tablet
@@ -131,6 +133,7 @@ These 5 patterns cover 80% of all layout needs. Master these first.
- `gap-6` - Consistent spacing between items
**When to use:**
- Dashboards
- Product grids
- Image galleries
@@ -171,17 +174,20 @@ These 5 patterns cover 80% of all layout needs. Master these first.
```
**Key Features:**
- `max-w-md` - Constrain form width (448px max)
- `mx-auto` - Center the form
- `space-y-4` - Vertical spacing between fields
- `w-full` - Full-width button
**Form width guidelines:**
- **Short forms** (login, signup): `max-w-md` (448px)
- **Medium forms** (profile, settings): `max-w-lg` (512px)
- **Long forms** (checkout): `max-w-2xl` (672px)
**When to use:**
- Login/signup forms
- Contact forms
- Settings forms
@@ -220,6 +226,7 @@ These 5 patterns cover 80% of all layout needs. Master these first.
```
**Key Features:**
- `flex` - Horizontal layout
- `w-64` - Fixed sidebar width (256px)
- `flex-1` - Main content takes remaining space
@@ -249,6 +256,7 @@ These 5 patterns cover 80% of all layout needs. Master these first.
```
**When to use:**
- Admin dashboards
- Settings pages
- Documentation sites
@@ -277,17 +285,20 @@ These 5 patterns cover 80% of all layout needs. Master these first.
```
**Key Features:**
- `max-w-2xl` - Optimal reading width (672px)
- `mx-auto` - Center content
- `prose` - Typography styles (if using @tailwindcss/typography)
**Width recommendations:**
- **Articles/Blogs**: `max-w-2xl` (672px)
- **Documentation**: `max-w-3xl` (768px)
- **Landing pages**: `max-w-4xl` (896px) or wider
- **Forms**: `max-w-md` (448px)
**When to use:**
- Blog posts
- Articles
- Documentation
@@ -327,13 +338,13 @@ Always start with mobile layout, then enhance for larger screens:
### Breakpoints
| Breakpoint | Min Width | Typical Use |
|------------|-----------|-------------|
| `sm:` | 640px | Large phones, small tablets |
| `md:` | 768px | Tablets |
| `lg:` | 1024px | Laptops, desktops |
| `xl:` | 1280px | Large desktops |
| `2xl:` | 1536px | Extra large screens |
| Breakpoint | Min Width | Typical Use |
| ---------- | --------- | --------------------------- |
| `sm:` | 640px | Large phones, small tablets |
| `md:` | 768px | Tablets |
| `lg:` | 1024px | Laptops, desktops |
| `xl:` | 1280px | Large desktops |
| `2xl:` | 1536px | Extra large screens |
### Responsive Grid Columns
@@ -457,12 +468,8 @@ grid-cols-1 lg:grid-cols-3
```tsx
// 2/3 - 1/3 split
<div className="grid grid-cols-3 gap-6">
<div className="col-span-2">
Main content (2/3 width)
</div>
<div className="col-span-1">
Sidebar (1/3 width)
</div>
<div className="col-span-2">Main content (2/3 width)</div>
<div className="col-span-1">Sidebar (1/3 width)</div>
</div>
```
@@ -482,12 +489,8 @@ grid-cols-1 lg:grid-cols-3
```tsx
<div className="flex gap-6">
<aside className="sticky top-6 h-fit w-64">
{/* Stays in view while scrolling */}
</aside>
<main className="flex-1">
{/* Scrollable content */}
</main>
<aside className="sticky top-6 h-fit w-64">{/* Stays in view while scrolling */}</aside>
<main className="flex-1">{/* Scrollable content */}</main>
</div>
```
@@ -579,6 +582,7 @@ w-full px-4
---
**Related Documentation:**
- [Spacing Philosophy](./04-spacing-philosophy.md) - When to use margin vs padding vs gap
- [Foundations](./01-foundations.md) - Spacing tokens and scale
- [Quick Start](./00-quick-start.md) - Essential patterns

View File

@@ -21,6 +21,7 @@
These 5 rules eliminate 90% of spacing inconsistencies:
### Rule 1: Parent Controls Children
**Children don't add their own margins. The parent controls spacing between siblings.**
```tsx
@@ -40,6 +41,7 @@ These 5 rules eliminate 90% of spacing inconsistencies:
```
**Why this matters:**
- Eliminates "last child" edge cases
- Makes components reusable (they work in any context)
- Changes propagate from one place (parent)
@@ -48,6 +50,7 @@ These 5 rules eliminate 90% of spacing inconsistencies:
---
### Rule 2: Use Gap for Siblings
**For flex and grid layouts, use `gap-*` to space siblings.**
```tsx
@@ -73,6 +76,7 @@ These 5 rules eliminate 90% of spacing inconsistencies:
---
### Rule 3: Use Padding for Internal Spacing
**Padding is for spacing _inside_ a component, between the border and content.**
```tsx
@@ -91,6 +95,7 @@ These 5 rules eliminate 90% of spacing inconsistencies:
---
### Rule 4: Use space-y for Vertical Stacks
**For vertical stacks (not flex/grid), use `space-y-*` utility.**
```tsx
@@ -110,6 +115,7 @@ These 5 rules eliminate 90% of spacing inconsistencies:
```
**How space-y works:**
```css
/* space-y-4 applies margin-top to all children except first */
.space-y-4 > * + * {
@@ -120,6 +126,7 @@ These 5 rules eliminate 90% of spacing inconsistencies:
---
### Rule 5: Margins Only for Exceptions
**Use margin only when a specific child needs different spacing from its siblings.**
```tsx
@@ -151,18 +158,19 @@ When children control their own margins:
```tsx
// ❌ ANTI-PATTERN
function TodoItem({ className }: { className?: string }) {
return <div className={cn("mb-4", className)}>Todo</div>;
return <div className={cn('mb-4', className)}>Todo</div>;
}
// Usage
<div>
<TodoItem /> {/* Has mb-4 */}
<TodoItem /> {/* Has mb-4 */}
<TodoItem /> {/* Has mb-4 - unwanted margin at bottom! */}
</div>
<TodoItem /> {/* Has mb-4 */}
<TodoItem /> {/* Has mb-4 */}
<TodoItem /> {/* Has mb-4 - unwanted margin at bottom! */}
</div>;
```
**Problems:**
1. ❌ Last item has unwanted margin
2. ❌ Can't change spacing without modifying component
3. ❌ Margin collapsing creates unpredictable spacing
@@ -199,6 +207,7 @@ function TodoItem({ className }: { className?: string }) {
```
**Benefits:**
1. ✅ No edge cases (last child, first child, only child)
2. ✅ Spacing controlled in one place
3. ✅ Component works in any layout context
@@ -265,6 +274,7 @@ Use this flowchart to choose the right spacing method:
```
**Spacing breakdown:**
- `space-y-4` on form: 16px between field groups
- `space-y-2` on field group: 8px between label and input
- No margins on children
@@ -288,6 +298,7 @@ Use this flowchart to choose the right spacing method:
```
**Why gap over space-x:**
- Works with `flex-wrap`
- Works with `flex-col` (changes direction)
- Consistent spacing in all directions
@@ -306,6 +317,7 @@ Use this flowchart to choose the right spacing method:
```
**Why gap:**
- Consistent spacing between rows and columns
- Works with responsive grid changes
- No edge cases (first row, last column, etc.)
@@ -332,6 +344,7 @@ Use this flowchart to choose the right spacing method:
```
**Spacing breakdown:**
- `p-6` on Card: 24px internal padding
- `space-y-4` on CardContent: 16px between paragraphs
- `pt-4` on CardFooter: Additional top padding for visual separation
@@ -364,6 +377,7 @@ Use this flowchart to choose the right spacing method:
```
**Spacing breakdown:**
- `px-4`: Horizontal padding (prevents edge touching)
- `py-8`: Vertical padding (top and bottom spacing)
- `space-y-6`: 24px between sections
@@ -376,25 +390,28 @@ Use this flowchart to choose the right spacing method:
### Example 1: Button Group
#### ❌ Before (Child-Controlled)
```tsx
function ActionButton({ children, className }: Props) {
return <Button className={cn("mr-4", className)}>{children}</Button>;
return <Button className={cn('mr-4', className)}>{children}</Button>;
}
// Usage
<div className="flex">
<ActionButton>Cancel</ActionButton>
<ActionButton>Save</ActionButton>
<ActionButton>Delete</ActionButton> {/* Unwanted mr-4 */}
</div>
<ActionButton>Delete</ActionButton> {/* Unwanted mr-4 */}
</div>;
```
**Problems:**
- Last button has unwanted margin
- Can't change spacing without modifying component
- Hard to use in vertical layout
#### ✅ After (Parent-Controlled)
```tsx
function ActionButton({ children, className }: Props) {
return <Button className={className}>{children}</Button>;
@@ -415,6 +432,7 @@ function ActionButton({ children, className }: Props) {
```
**Benefits:**
- No edge cases
- Reusable in any layout
- Easy to change spacing
@@ -424,6 +442,7 @@ function ActionButton({ children, className }: Props) {
### Example 2: List Items
#### ❌ Before (Child-Controlled)
```tsx
function ListItem({ title, description }: Props) {
return (
@@ -437,16 +456,18 @@ function ListItem({ title, description }: Props) {
<div>
<ListItem title="Item 1" description="..." />
<ListItem title="Item 2" description="..." />
<ListItem title="Item 3" description="..." /> {/* Unwanted mb-6 */}
</div>
<ListItem title="Item 3" description="..." /> {/* Unwanted mb-6 */}
</div>;
```
**Problems:**
- Last item has unwanted bottom margin
- Can't change list spacing without modifying component
- Internal `mb-2` hard to override
#### ✅ After (Parent-Controlled)
```tsx
function ListItem({ title, description }: Props) {
return (
@@ -473,6 +494,7 @@ function ListItem({ title, description }: Props) {
```
**Benefits:**
- No unwanted margins
- Internal spacing controlled by `space-y-2`
- Reusable with different spacings
@@ -482,6 +504,7 @@ function ListItem({ title, description }: Props) {
### Example 3: Form Fields
#### ❌ Before (Mixed Strategy)
```tsx
<form>
<div className="mb-4">
@@ -494,16 +517,20 @@ function ListItem({ title, description }: Props) {
<Input id="email" className="mt-2" />
</div>
<Button type="submit" className="mt-6">Submit</Button>
<Button type="submit" className="mt-6">
Submit
</Button>
</form>
```
**Problems:**
- Spacing scattered across children
- Hard to change consistently
- Have to remember `mt-6` for button
#### ✅ After (Parent-Controlled)
```tsx
<form className="space-y-4">
<div className="space-y-2">
@@ -516,11 +543,14 @@ function ListItem({ title, description }: Props) {
<Input id="email" />
</div>
<Button type="submit" className="mt-2">Submit</Button>
<Button type="submit" className="mt-2">
Submit
</Button>
</form>
```
**Benefits:**
- Spacing controlled in 2 places: form (`space-y-4`) and field groups (`space-y-2`)
- Easy to change all field spacing at once
- Consistent and predictable
@@ -533,18 +563,20 @@ function ListItem({ title, description }: Props) {
```tsx
// ❌ WRONG
{items.map((item, index) => (
<Card key={item.id} className={index < items.length - 1 ? "mb-4" : ""}>
{item.name}
</Card>
))}
{
items.map((item, index) => (
<Card key={item.id} className={index < items.length - 1 ? 'mb-4' : ''}>
{item.name}
</Card>
));
}
// ✅ CORRECT
<div className="space-y-4">
{items.map(item => (
{items.map((item) => (
<Card key={item.id}>{item.name}</Card>
))}
</div>
</div>;
```
---
@@ -564,6 +596,7 @@ function ListItem({ title, description }: Props) {
```
**Why negative margins are bad:**
- Indicates broken spacing strategy
- Hard to maintain
- Creates coupling between components
@@ -618,26 +651,26 @@ function ListItem({ title, description }: Props) {
### Spacing Method Cheat Sheet
| Use Case | Method | Example |
|----------|--------|---------|
| **Flex siblings** | `gap-*` | `flex gap-4` |
| **Grid siblings** | `gap-*` | `grid gap-6` |
| **Vertical stack** | `space-y-*` | `space-y-4` |
| **Horizontal stack** | `space-x-*` | `space-x-2` |
| **Inside component** | `p-*` | `p-6` |
| **One child exception** | `m-*` | `mt-8` |
| Use Case | Method | Example |
| ----------------------- | ----------- | ------------ |
| **Flex siblings** | `gap-*` | `flex gap-4` |
| **Grid siblings** | `gap-*` | `grid gap-6` |
| **Vertical stack** | `space-y-*` | `space-y-4` |
| **Horizontal stack** | `space-x-*` | `space-x-2` |
| **Inside component** | `p-*` | `p-6` |
| **One child exception** | `m-*` | `mt-8` |
### Common Spacing Values
| Class | Pixels | Usage |
|-------|--------|-------|
| `gap-2` or `space-y-2` | 8px | Tight (label + input) |
| `gap-4` or `space-y-4` | 16px | Standard (form fields) |
| `gap-6` or `space-y-6` | 24px | Sections (cards) |
| `gap-8` or `space-y-8` | 32px | Large gaps |
| `p-4` | 16px | Standard padding |
| `p-6` | 24px | Card padding |
| `px-4 py-8` | 16px / 32px | Page padding |
| Class | Pixels | Usage |
| ---------------------- | ----------- | ---------------------- |
| `gap-2` or `space-y-2` | 8px | Tight (label + input) |
| `gap-4` or `space-y-4` | 16px | Standard (form fields) |
| `gap-6` or `space-y-6` | 24px | Sections (cards) |
| `gap-8` or `space-y-8` | 32px | Large gaps |
| `p-4` | 16px | Standard padding |
| `p-6` | 24px | Card padding |
| `px-4 py-8` | 16px / 32px | Page padding |
### Decision Flowchart (Simplified)
@@ -682,7 +715,7 @@ Need spacing?
Before implementing spacing, verify:
- [ ] **Parent controls children?** Using gap or space-y/x?
- [ ] **No child margins?** Components don't have mb-* or mr-*?
- [ ] **No child margins?** Components don't have mb-_ or mr-_?
- [ ] **Consistent method?** Not mixing gap + child margins?
- [ ] **Reusable components?** Work in different contexts?
- [ ] **No edge cases?** No last-child or first-child special handling?
@@ -700,6 +733,7 @@ Before implementing spacing, verify:
---
**Related Documentation:**
- [Layouts](./03-layouts.md) - When to use Grid vs Flex
- [Foundations](./01-foundations.md) - Spacing scale tokens
- [Component Creation](./05-component-creation.md) - Building reusable components

View File

@@ -22,6 +22,7 @@
**80% of the time, you should COMPOSE existing shadcn/ui components.**
Only create custom components when:
1. ✅ You're reusing the same composition 3+ times
2. ✅ The pattern has complex business logic
3. ✅ You need variants beyond what shadcn/ui provides
@@ -74,6 +75,7 @@ Do you need a UI element?
```
**Why this is good:**
- Simple and direct
- Easy to customize per use case
- No abstraction overhead
@@ -103,10 +105,11 @@ function ContentCard({ title, description, content, actionLabel, onAction }: Pro
}
// Used once... why did we create this?
<ContentCard title="..." description="..." content="..." />
<ContentCard title="..." description="..." content="..." />;
```
**Problems:**
- ❌ Created before knowing if pattern is reused
- ❌ Inflexible (what if we need 2 buttons?)
- ❌ Unclear what it renders (abstraction hides structure)
@@ -148,6 +151,7 @@ function DashboardMetricCard({
```
**Why this works:**
- ✅ Pattern validated (used 3+ times)
- ✅ Specific purpose (dashboard metrics)
- ✅ Consistent structure across uses
@@ -171,22 +175,23 @@ interface MyComponentProps {
export function MyComponent({ className, children }: MyComponentProps) {
return (
<div className={cn(
"base-classes-here", // Base styles
className // Allow overrides
)}>
<div
className={cn(
'base-classes-here', // Base styles
className // Allow overrides
)}
>
{children}
</div>
);
}
// Usage
<MyComponent className="custom-overrides">
Content
</MyComponent>
<MyComponent className="custom-overrides">Content</MyComponent>;
```
**Key points:**
- Always accept `className` prop
- Use `cn()` utility for merging
- Base classes first, overrides last
@@ -203,24 +208,24 @@ import { cn } from '@/lib/utils';
const componentVariants = cva(
// Base classes (always applied)
"inline-flex items-center justify-center rounded-lg font-medium transition-colors",
'inline-flex items-center justify-center rounded-lg font-medium transition-colors',
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
ghost: "hover:bg-accent hover:text-accent-foreground",
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
ghost: 'hover:bg-accent hover:text-accent-foreground',
},
size: {
sm: "h-8 px-3 text-xs",
default: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base",
sm: 'h-8 px-3 text-xs',
default: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
},
},
defaultVariants: {
variant: "default",
size: "default",
variant: 'default',
size: 'default',
},
}
);
@@ -231,25 +236,18 @@ interface MyComponentProps
// Additional props here
}
export function MyComponent({
variant,
size,
className,
...props
}: MyComponentProps) {
return (
<div
className={cn(componentVariants({ variant, size, className }))}
{...props}
/>
);
export function MyComponent({ variant, size, className, ...props }: MyComponentProps) {
return <div className={cn(componentVariants({ variant, size, className }))} {...props} />;
}
// Usage
<MyComponent variant="outline" size="lg">Content</MyComponent>
<MyComponent variant="outline" size="lg">
Content
</MyComponent>;
```
**Key points:**
- Use CVA for complex variant logic
- Always provide `defaultVariants`
- Extend `React.HTMLAttributes` for standard HTML props
@@ -273,13 +271,7 @@ interface StatCardProps {
className?: string;
}
export function StatCard({
title,
value,
description,
icon,
className,
}: StatCardProps) {
export function StatCard({ title, value, description, icon, className }: StatCardProps) {
return (
<Card className={className}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
@@ -288,9 +280,7 @@ export function StatCard({
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{value}</div>
{description && (
<p className="text-xs text-muted-foreground">{description}</p>
)}
{description && <p className="text-xs text-muted-foreground">{description}</p>}
</CardContent>
</Card>
);
@@ -302,10 +292,11 @@ export function StatCard({
value="1,234"
description="+12% from last month"
icon={<Users className="h-4 w-4 text-muted-foreground" />}
/>
/>;
```
**Key points:**
- Compose from shadcn/ui primitives
- Keep structure consistent
- Optional props with `?`
@@ -354,14 +345,17 @@ export function Toggle({
}
// Uncontrolled usage
<Toggle defaultValue={false}>Auto-save</Toggle>
<Toggle defaultValue={false}>Auto-save</Toggle>;
// Controlled usage
const [enabled, setEnabled] = useState(false);
<Toggle value={enabled} onChange={setEnabled}>Auto-save</Toggle>
<Toggle value={enabled} onChange={setEnabled}>
Auto-save
</Toggle>;
```
**Key points:**
- Support both controlled and uncontrolled modes
- Use `defaultValue` for initial uncontrolled value
- Use `value` + `onChange` for controlled mode
@@ -376,6 +370,7 @@ const [enabled, setEnabled] = useState(false);
**class-variance-authority** (CVA) is a utility for creating component variants with Tailwind CSS.
**Why use CVA?**
- ✅ Type-safe variant props
- ✅ Compound variants (combinations)
- ✅ Default variants
@@ -390,24 +385,23 @@ import { cva } from 'class-variance-authority';
const alertVariants = cva(
// Base classes (always applied)
"relative w-full rounded-lg border p-4",
'relative w-full rounded-lg border p-4',
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
default: 'bg-background text-foreground',
destructive:
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
},
},
defaultVariants: {
variant: "default",
variant: 'default',
},
}
);
// Usage
<div className={alertVariants({ variant: "destructive" })}>
Alert content
</div>
<div className={alertVariants({ variant: 'destructive' })}>Alert content</div>;
```
---
@@ -416,32 +410,32 @@ const alertVariants = cva(
```tsx
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md font-medium transition-colors",
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent",
ghost: "hover:bg-accent hover:text-accent-foreground",
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent',
ghost: 'hover:bg-accent hover:text-accent-foreground',
},
size: {
sm: "h-8 px-3 text-xs",
default: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base",
sm: 'h-8 px-3 text-xs',
default: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
},
},
defaultVariants: {
variant: "default",
size: "default",
variant: 'default',
size: 'default',
},
}
);
// Usage
<button className={buttonVariants({ variant: "outline", size: "lg" })}>
<button className={buttonVariants({ variant: 'outline', size: 'lg' })}>
Large Outline Button
</button>
</button>;
```
---
@@ -451,28 +445,28 @@ const buttonVariants = cva(
**Use case**: Different classes when specific variant combinations are used
```tsx
const buttonVariants = cva("base-classes", {
const buttonVariants = cva('base-classes', {
variants: {
variant: {
default: "bg-primary",
destructive: "bg-destructive",
default: 'bg-primary',
destructive: 'bg-destructive',
},
size: {
sm: "h-8",
lg: "h-12",
sm: 'h-8',
lg: 'h-12',
},
},
// Compound variants: specific combinations
compoundVariants: [
{
variant: "destructive",
size: "lg",
class: "text-lg font-bold", // Applied when BOTH are true
variant: 'destructive',
size: 'lg',
class: 'text-lg font-bold', // Applied when BOTH are true
},
],
defaultVariants: {
variant: "default",
size: "sm",
variant: 'default',
size: 'sm',
},
});
```
@@ -484,6 +478,7 @@ const buttonVariants = cva("base-classes", {
### Prop Naming Conventions
**DO**:
```tsx
// ✅ Descriptive, semantic names
interface UserCardProps {
@@ -495,6 +490,7 @@ interface UserCardProps {
```
**DON'T**:
```tsx
// ❌ Generic, unclear names
interface CardProps {
@@ -510,6 +506,7 @@ interface CardProps {
### Required vs Optional Props
**Guidelines:**
- Required: Core functionality depends on it
- Optional: Nice-to-have, has sensible default
@@ -531,7 +528,7 @@ interface AlertProps {
export function Alert({
children,
variant = 'default', // Default for optional prop
variant = 'default', // Default for optional prop
onClose,
icon,
className,
@@ -545,6 +542,7 @@ export function Alert({
### Prop Type Patterns
**Enum props** (limited options):
```tsx
interface ButtonProps {
variant: 'default' | 'destructive' | 'outline';
@@ -553,6 +551,7 @@ interface ButtonProps {
```
**Boolean flags**:
```tsx
interface CardProps {
isLoading?: boolean;
@@ -562,6 +561,7 @@ interface CardProps {
```
**Callback props**:
```tsx
interface FormProps {
onSubmit: (data: FormData) => void;
@@ -571,6 +571,7 @@ interface FormProps {
```
**Render props** (advanced customization):
```tsx
interface ListProps<T> {
items: T[];
@@ -583,7 +584,7 @@ interface ListProps<T> {
items={users}
renderItem={(user, i) => <UserCard key={i} user={user} />}
renderEmpty={() => <EmptyState />}
/>
/>;
```
---
@@ -593,6 +594,7 @@ interface ListProps<T> {
Before shipping a custom component, verify:
### Visual Testing
- [ ] **Light mode** - Component looks correct
- [ ] **Dark mode** - Component looks correct (toggle theme)
- [ ] **All variants** - Test each variant works
@@ -602,6 +604,7 @@ Before shipping a custom component, verify:
- [ ] **Empty state** - Handles no data gracefully
### Accessibility Testing
- [ ] **Keyboard navigation** - Can be focused and activated with Tab/Enter
- [ ] **Focus indicators** - Visible focus ring (`:focus-visible`)
- [ ] **Screen reader** - ARIA labels and roles present
@@ -609,6 +612,7 @@ Before shipping a custom component, verify:
- [ ] **Semantic HTML** - Using correct HTML elements (button, nav, etc.)
### Functional Testing
- [ ] **Props work** - All props apply correctly
- [ ] **className override** - Can override styles with className prop
- [ ] **Controlled/uncontrolled** - Both modes work (if applicable)
@@ -616,6 +620,7 @@ Before shipping a custom component, verify:
- [ ] **TypeScript** - No type errors, props autocomplete
### Code Quality
- [ ] **No console errors** - Check browser console
- [ ] **No warnings** - React warnings, a11y warnings
- [ ] **Performance** - No unnecessary re-renders
@@ -644,13 +649,7 @@ interface StatCardProps {
className?: string;
}
export function StatCard({
title,
value,
change,
icon: Icon,
className,
}: StatCardProps) {
export function StatCard({ title, value, change, icon: Icon, className }: StatCardProps) {
return (
<Card className={className}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
@@ -660,11 +659,9 @@ export function StatCard({
<CardContent>
<div className="text-2xl font-bold">{value}</div>
{change !== undefined && (
<p className={cn(
"text-xs",
change >= 0 ? "text-green-600" : "text-destructive"
)}>
{change >= 0 ? '+' : ''}{change}% from last month
<p className={cn('text-xs', change >= 0 ? 'text-green-600' : 'text-destructive')}>
{change >= 0 ? '+' : ''}
{change}% from last month
</p>
)}
</CardContent>
@@ -678,10 +675,11 @@ export function StatCard({
<StatCard title="Subscriptions" value="+2350" change={12.5} icon={Users} />
<StatCard title="Sales" value="+12,234" change={19} icon={CreditCard} />
<StatCard title="Active Now" value="+573" change={-2.1} icon={Activity} />
</div>
</div>;
```
**Why this works:**
- Specific purpose (dashboard metrics)
- Reused 8+ times
- Consistent structure
@@ -747,18 +745,10 @@ export function ConfirmDialog({
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isLoading}
>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isLoading}>
{cancelLabel}
</Button>
<Button
variant={variant}
onClick={handleConfirm}
disabled={isLoading}
>
<Button variant={variant} onClick={handleConfirm} disabled={isLoading}>
{isLoading ? 'Processing...' : confirmLabel}
</Button>
</DialogFooter>
@@ -781,10 +771,11 @@ const [showDeleteDialog, setShowDeleteDialog] = useState(false);
await deleteUser(user.id);
toast.success('User deleted');
}}
/>
/>;
```
**Why this works:**
- Common pattern (confirmations)
- Handles loading states automatically
- Consistent UX across app
@@ -808,19 +799,12 @@ interface PageHeaderProps {
className?: string;
}
export function PageHeader({
title,
description,
action,
className,
}: PageHeaderProps) {
export function PageHeader({ title, description, action, className }: PageHeaderProps) {
return (
<div className={cn("flex items-center justify-between", className)}>
<div className={cn('flex items-center justify-between', className)}>
<div className="space-y-1">
<h1 className="text-3xl font-bold tracking-tight">{title}</h1>
{description && (
<p className="text-muted-foreground">{description}</p>
)}
{description && <p className="text-muted-foreground">{description}</p>}
</div>
{action && <div>{action}</div>}
</div>
@@ -837,7 +821,7 @@ export function PageHeader({
Create User
</Button>
}
/>
/>;
```
---
@@ -866,6 +850,7 @@ Before creating a custom component, ask:
---
**Related Documentation:**
- [Components](./02-components.md) - shadcn/ui component library
- [AI Guidelines](./08-ai-guidelines.md) - Component templates for AI
- [Forms](./06-forms.md) - Form component patterns

View File

@@ -27,6 +27,7 @@
- **shadcn/ui components** - Input, Label, Button, etc.
**Why this stack?**
- ✅ Type-safe validation (TypeScript + Zod)
- ✅ Minimal re-renders (react-hook-form)
- ✅ Accessible by default (shadcn/ui)
@@ -80,11 +81,7 @@ export function SimpleForm() {
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
{...form.register('email')}
/>
<Input id="email" type="email" {...form.register('email')} />
</div>
<Button type="submit">Submit</Button>
@@ -180,6 +177,7 @@ export function LoginForm() {
```
**Key points:**
1. Define Zod schema first
2. Infer TypeScript type with `z.infer`
3. Use `zodResolver` in `useForm`
@@ -217,15 +215,9 @@ export function LoginForm() {
```tsx
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
rows={4}
{...form.register('description')}
/>
<Textarea id="description" rows={4} {...form.register('description')} />
{form.formState.errors.description && (
<p className="text-sm text-destructive">
{form.formState.errors.description.message}
</p>
<p className="text-sm text-destructive">{form.formState.errors.description.message}</p>
)}
</div>
```
@@ -237,10 +229,7 @@ export function LoginForm() {
```tsx
<div className="space-y-2">
<Label htmlFor="role">Role</Label>
<Select
value={form.watch('role')}
onValueChange={(value) => form.setValue('role', value)}
>
<Select value={form.watch('role')} onValueChange={(value) => form.setValue('role', value)}>
<SelectTrigger id="role">
<SelectValue placeholder="Select a role" />
</SelectTrigger>
@@ -251,9 +240,7 @@ export function LoginForm() {
</SelectContent>
</Select>
{form.formState.errors.role && (
<p className="text-sm text-destructive">
{form.formState.errors.role.message}
</p>
<p className="text-sm text-destructive">{form.formState.errors.role.message}</p>
)}
</div>
```
@@ -272,12 +259,12 @@ export function LoginForm() {
<Label htmlFor="terms" className="text-sm font-normal">
I accept the terms and conditions
</Label>
</div>
{form.formState.errors.acceptTerms && (
<p className="text-sm text-destructive">
{form.formState.errors.acceptTerms.message}
</p>
)}
</div>;
{
form.formState.errors.acceptTerms && (
<p className="text-sm text-destructive">{form.formState.errors.acceptTerms.message}</p>
);
}
```
---
@@ -289,22 +276,16 @@ export function LoginForm() {
<Label>Notification Method</Label>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<input
type="radio"
id="email"
value="email"
{...form.register('notificationMethod')}
/>
<Label htmlFor="email" className="font-normal">Email</Label>
<input type="radio" id="email" value="email" {...form.register('notificationMethod')} />
<Label htmlFor="email" className="font-normal">
Email
</Label>
</div>
<div className="flex items-center space-x-2">
<input
type="radio"
id="sms"
value="sms"
{...form.register('notificationMethod')}
/>
<Label htmlFor="sms" className="font-normal">SMS</Label>
<input type="radio" id="sms" value="sms" {...form.register('notificationMethod')} />
<Label htmlFor="sms" className="font-normal">
SMS
</Label>
</div>
</div>
</div>
@@ -320,65 +301,68 @@ export function LoginForm() {
import { z } from 'zod';
// Email
z.string().email('Invalid email address')
z.string().email('Invalid email address');
// Min/max length
z.string().min(8, 'Minimum 8 characters').max(100, 'Maximum 100 characters')
z.string().min(8, 'Minimum 8 characters').max(100, 'Maximum 100 characters');
// Required field
z.string().min(1, 'This field is required')
z.string().min(1, 'This field is required');
// Optional field
z.string().optional()
z.string().optional();
// Number with range
z.number().min(0).max(100)
z.number().min(0).max(100);
// Number from string input
z.coerce.number().min(0)
z.coerce.number().min(0);
// Enum
z.enum(['admin', 'user', 'guest'], {
errorMap: () => ({ message: 'Invalid role' })
})
errorMap: () => ({ message: 'Invalid role' }),
});
// URL
z.string().url('Invalid URL')
z.string().url('Invalid URL');
// Password with requirements
z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one number')
.regex(/[0-9]/, 'Password must contain at least one number');
// Confirm password
z.object({
password: z.string().min(8),
confirmPassword: z.string()
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
})
path: ['confirmPassword'],
});
// Custom validation
z.string().refine((val) => !val.includes('badword'), {
message: 'Invalid input',
})
});
// Conditional fields
z.object({
role: z.enum(['admin', 'user']),
adminKey: z.string().optional(),
}).refine((data) => {
if (data.role === 'admin') {
return !!data.adminKey;
}).refine(
(data) => {
if (data.role === 'admin') {
return !!data.adminKey;
}
return true;
},
{
message: 'Admin key required for admin role',
path: ['adminKey'],
}
return true;
}, {
message: 'Admin key required for admin role',
path: ['adminKey'],
})
);
```
---
@@ -448,6 +432,7 @@ type UserFormData = z.infer<typeof userFormSchema>;
```
**Accessibility notes:**
- Use `aria-invalid` to indicate error state
- Use `aria-describedby` to link error message
- Error ID format: `{fieldName}-error`
@@ -470,14 +455,14 @@ const onSubmit = async (data: FormData) => {
};
// Display form-level error
{form.formState.errors.root && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
{form.formState.errors.root.message}
</AlertDescription>
</Alert>
)}
{
form.formState.errors.root && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{form.formState.errors.root.message}</AlertDescription>
</Alert>
);
}
```
---
@@ -620,32 +605,26 @@ const onSubmit = async (data: FormData) => {
<div className="space-y-4">
<div>
<h3 className="text-lg font-semibold">Personal Information</h3>
<p className="text-sm text-muted-foreground">
Basic details about you
</p>
<p className="text-sm text-muted-foreground">Basic details about you</p>
</div>
<Separator />
<div className="space-y-4">
{/* Fields */}
</div>
<div className="space-y-4">{/* Fields */}</div>
</div>
{/* Section 2 */}
<div className="space-y-4">
<div>
<h3 className="text-lg font-semibold">Account Settings</h3>
<p className="text-sm text-muted-foreground">
Configure your account preferences
</p>
<p className="text-sm text-muted-foreground">Configure your account preferences</p>
</div>
<Separator />
<div className="space-y-4">
{/* Fields */}
</div>
<div className="space-y-4">{/* Fields */}</div>
</div>
<div className="flex justify-end gap-4">
<Button type="button" variant="outline">Cancel</Button>
<Button type="button" variant="outline">
Cancel
</Button>
<Button type="submit">Save Changes</Button>
</div>
</form>
@@ -661,10 +640,14 @@ const onSubmit = async (data: FormData) => {
import { useFieldArray } from 'react-hook-form';
const schema = z.object({
items: z.array(z.object({
name: z.string().min(1),
quantity: z.coerce.number().min(1),
})).min(1, 'At least one item required'),
items: z
.array(
z.object({
name: z.string().min(1),
quantity: z.coerce.number().min(1),
})
)
.min(1, 'At least one item required'),
});
function DynamicForm() {
@@ -684,30 +667,19 @@ function DynamicForm() {
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex gap-4">
<Input
{...form.register(`items.${index}.name`)}
placeholder="Item name"
/>
<Input {...form.register(`items.${index}.name`)} placeholder="Item name" />
<Input
type="number"
{...form.register(`items.${index}.quantity`)}
placeholder="Quantity"
/>
<Button
type="button"
variant="destructive"
onClick={() => remove(index)}
>
<Button type="button" variant="destructive" onClick={() => remove(index)}>
Remove
</Button>
</div>
))}
<Button
type="button"
variant="outline"
onClick={() => append({ name: '', quantity: 1 })}
>
<Button type="button" variant="outline" onClick={() => append({ name: '', quantity: 1 })}>
Add Item
</Button>
@@ -722,18 +694,23 @@ function DynamicForm() {
### Conditional Fields
```tsx
const schema = z.object({
role: z.enum(['user', 'admin']),
adminKey: z.string().optional(),
}).refine((data) => {
if (data.role === 'admin') {
return !!data.adminKey;
}
return true;
}, {
message: 'Admin key required',
path: ['adminKey'],
});
const schema = z
.object({
role: z.enum(['user', 'admin']),
adminKey: z.string().optional(),
})
.refine(
(data) => {
if (data.role === 'admin') {
return !!data.adminKey;
}
return true;
},
{
message: 'Admin key required',
path: ['adminKey'],
}
);
function ConditionalForm() {
const form = useForm({ resolver: zodResolver(schema) });
@@ -741,23 +718,17 @@ function ConditionalForm() {
return (
<form className="space-y-4">
<Select
value={role}
onValueChange={(val) => form.setValue('role', val as any)}
>
<SelectTrigger><SelectValue /></SelectTrigger>
<Select value={role} onValueChange={(val) => form.setValue('role', val as any)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="user">User</SelectItem>
<SelectItem value="admin">Admin</SelectItem>
</SelectContent>
</Select>
{role === 'admin' && (
<Input
{...form.register('adminKey')}
placeholder="Admin Key"
/>
)}
{role === 'admin' && <Input {...form.register('adminKey')} placeholder="Admin Key" />}
</form>
);
}
@@ -774,14 +745,10 @@ const schema = z.object({
}),
});
<input
type="file"
{...form.register('file')}
accept="image/*"
/>
<input type="file" {...form.register('file')} accept="image/*" />;
const onSubmit = (data: FormData) => {
const file = data.file[0]; // FileList -> File
const file = data.file[0]; // FileList -> File
const formData = new FormData();
formData.append('file', file);
// Upload formData
@@ -795,6 +762,7 @@ const onSubmit = (data: FormData) => {
Before shipping a form, verify:
### Functionality
- [ ] All fields register correctly
- [ ] Validation works (test invalid inputs)
- [ ] Submit handler fires
@@ -803,6 +771,7 @@ Before shipping a form, verify:
- [ ] Success case redirects/shows success
### Accessibility
- [ ] Labels associated with inputs (`htmlFor` + `id`)
- [ ] Error messages use `aria-describedby`
- [ ] Invalid inputs have `aria-invalid`
@@ -810,6 +779,7 @@ Before shipping a form, verify:
- [ ] Submit button disabled during submission
### UX
- [ ] Field errors appear on blur or submit
- [ ] Loading state prevents double-submit
- [ ] Success message or redirect on success
@@ -827,11 +797,13 @@ Before shipping a form, verify:
---
**Related Documentation:**
- [Components](./02-components.md) - Input, Label, Button, Select
- [Layouts](./03-layouts.md) - Form layout patterns
- [Accessibility](./07-accessibility.md) - ARIA attributes for forms
**External Resources:**
- [react-hook-form Documentation](https://react-hook-form.com)
- [Zod Documentation](https://zod.dev)

View File

@@ -24,12 +24,14 @@
We follow **WCAG 2.1 Level AA** as the **minimum** standard.
**Why Level AA?**
- ✅ Required for most legal compliance (ADA, Section 508)
- ✅ Covers 95%+ of accessibility needs
- ✅ Achievable without major UX compromises
- ✅ Industry standard for modern web apps
**WCAG Principles (POUR):**
1. **Perceivable** - Information can be perceived by users
2. **Operable** - Interface can be operated by users
3. **Understandable** - Information and operation are understandable
@@ -63,14 +65,15 @@ Creating a UI element?
### Minimum Contrast Ratios (WCAG AA)
| Content Type | Minimum Ratio | Example |
|--------------|---------------|---------|
| **Normal text** (< 18px) | **4.5:1** | Body paragraphs, form labels |
| **Large text** (≥ 18px or ≥ 14px bold) | **3:1** | Headings, subheadings |
| **UI components** | **3:1** | Buttons, form borders, icons |
| **Graphical objects** | **3:1** | Chart elements, infographics |
| Content Type | Minimum Ratio | Example |
| -------------------------------------- | ------------- | ---------------------------- |
| **Normal text** (< 18px) | **4.5:1** | Body paragraphs, form labels |
| **Large text** (≥ 18px or ≥ 14px bold) | **3:1** | Headings, subheadings |
| **UI components** | **3:1** | Buttons, form borders, icons |
| **Graphical objects** | **3:1** | Chart elements, infographics |
**WCAG AAA (ideal, not required):**
- Normal text: 7:1
- Large text: 4.5:1
@@ -79,6 +82,7 @@ Creating a UI element?
### Testing Color Contrast
**Tools:**
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
- Chrome DevTools: Inspect element → Accessibility panel
- [Contrast Ratio Tool](https://contrast-ratio.com)
@@ -104,6 +108,7 @@ Creating a UI element?
```
**Our design system tokens are WCAG AA compliant:**
- `text-foreground` on `bg-background`: 12.6:1 ✅
- `text-primary-foreground` on `bg-primary`: 8.2:1 ✅
- `text-destructive` on `bg-background`: 5.1:1 ✅
@@ -116,6 +121,7 @@ Creating a UI element?
**8% of men and 0.5% of women** have some form of color blindness.
**Best practices:**
- ❌ Don't rely on color alone to convey information
- ✅ Use icons, text labels, or patterns in addition to color
- ✅ Test with color blindness simulators
@@ -148,6 +154,7 @@ Creating a UI element?
### Core Requirements
All interactive elements must be:
1.**Focusable** - Can be reached with Tab key
2.**Activatable** - Can be triggered with Enter or Space
3.**Navigable** - Can move between with arrow keys (where appropriate)
@@ -176,6 +183,7 @@ All interactive elements must be:
```
**When to use `tabIndex`:**
- `tabIndex={0}` - Make non-interactive element focusable
- `tabIndex={-1}` - Remove from tab order (for programmatic focus)
- `tabIndex={1+}` - ❌ **Avoid** - Breaks natural order
@@ -184,31 +192,31 @@ All interactive elements must be:
### Keyboard Shortcuts
| Key | Action | Example |
|-----|--------|---------|
| **Tab** | Move focus forward | Navigate through form fields |
| **Shift + Tab** | Move focus backward | Go back to previous field |
| **Enter** | Activate button/link | Submit form, follow link |
| **Space** | Activate button/checkbox | Toggle checkbox, click button |
| **Escape** | Close overlay | Close dialog, dropdown |
| **Arrow keys** | Navigate within component | Navigate dropdown items |
| **Home** | Jump to start | First item in list |
| **End** | Jump to end | Last item in list |
| Key | Action | Example |
| --------------- | ------------------------- | ----------------------------- |
| **Tab** | Move focus forward | Navigate through form fields |
| **Shift + Tab** | Move focus backward | Go back to previous field |
| **Enter** | Activate button/link | Submit form, follow link |
| **Space** | Activate button/checkbox | Toggle checkbox, click button |
| **Escape** | Close overlay | Close dialog, dropdown |
| **Arrow keys** | Navigate within component | Navigate dropdown items |
| **Home** | Jump to start | First item in list |
| **End** | Jump to end | Last item in list |
---
### Implementing Keyboard Navigation
**Button (automatic):**
```tsx
// ✅ Button is keyboard accessible by default
<Button onClick={handleClick}>
Click me
</Button>
<Button onClick={handleClick}>Click me</Button>
// Enter or Space triggers onClick
```
**Custom clickable div (needs work):**
```tsx
// ❌ BAD - Not keyboard accessible
<div onClick={handleClick}>
@@ -237,11 +245,12 @@ All interactive elements must be:
```
**Dropdown navigation:**
```tsx
<DropdownMenu>
<DropdownMenuContent>
<DropdownMenuItem>Edit</DropdownMenuItem> {/* Arrow down */}
<DropdownMenuItem>Delete</DropdownMenuItem> {/* Arrow down */}
<DropdownMenuItem>Edit</DropdownMenuItem> {/* Arrow down */}
<DropdownMenuItem>Delete</DropdownMenuItem> {/* Arrow down */}
</DropdownMenuContent>
</DropdownMenu>
// shadcn/ui handles arrow key navigation automatically
@@ -276,12 +285,14 @@ All interactive elements must be:
### Screen Reader Basics
**Popular screen readers:**
- **NVDA** (Windows) - Free, most popular for testing
- **JAWS** (Windows) - Industry standard, paid
- **VoiceOver** (macOS/iOS) - Built-in to Apple devices
- **TalkBack** (Android) - Built-in to Android
**What screen readers announce:**
- Semantic element type (button, link, heading, etc.)
- Element text content
- Element state (expanded, selected, disabled)
@@ -334,6 +345,7 @@ All interactive elements must be:
```
**Semantic elements:**
- `<header>` - Page header
- `<nav>` - Navigation
- `<main>` - Main content (only one per page)
@@ -364,6 +376,7 @@ All interactive elements must be:
```
**Icon-only buttons:**
```tsx
// ✅ GOOD - ARIA label
<Button size="icon" aria-label="Close dialog">
@@ -383,6 +396,7 @@ All interactive elements must be:
### Common ARIA Attributes
**ARIA roles:**
```tsx
<div role="button" tabIndex={0}>Custom Button</div>
<div role="alert">Error message</div>
@@ -391,6 +405,7 @@ All interactive elements must be:
```
**ARIA states:**
```tsx
<button aria-expanded={isOpen}>Toggle Menu</button>
<button aria-pressed={isActive}>Toggle</button>
@@ -399,6 +414,7 @@ All interactive elements must be:
```
**ARIA properties:**
```tsx
<button aria-label="Close">×</button>
<input aria-describedby="email-help" />
@@ -412,6 +428,7 @@ All interactive elements must be:
### Form Accessibility
**Label association:**
```tsx
// ✅ GOOD - Explicit association
<Label htmlFor="email">Email</Label>
@@ -423,6 +440,7 @@ All interactive elements must be:
```
**Error messages:**
```tsx
// ✅ GOOD - Linked with aria-describedby
<Label htmlFor="password">Password</Label>
@@ -444,6 +462,7 @@ All interactive elements must be:
```
**Required fields:**
```tsx
// ✅ GOOD - Marked as required
<Label htmlFor="name">
@@ -502,6 +521,7 @@ toast.success('User created');
```
**Use `:focus-visible` instead of `:focus`:**
- `:focus` - Shows on mouse click AND keyboard
- `:focus-visible` - Shows only on keyboard (better UX)
@@ -516,7 +536,7 @@ toast.success('User created');
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent>
{/* Focus trapped inside */}
<Input autoFocus /> {/* Focus first field */}
<Input autoFocus /> {/* Focus first field */}
<Button>Submit</Button>
</DialogContent>
</Dialog>
@@ -539,7 +559,7 @@ const handleDelete = () => {
inputRef.current?.focus();
};
<Input ref={inputRef} />
<Input ref={inputRef} />;
```
---
@@ -549,11 +569,13 @@ const handleDelete = () => {
### Automated Testing Tools
**Browser extensions:**
- [axe DevTools](https://www.deque.com/axe/devtools/) - Free, comprehensive
- [WAVE](https://wave.webaim.org/extension/) - Visual feedback
- [Lighthouse](https://developer.chrome.com/docs/lighthouse/) - Built into Chrome
**CI/CD testing:**
- [@axe-core/react](https://github.com/dequelabs/axe-core-npm/tree/develop/packages/react) - Runtime accessibility testing
- [jest-axe](https://github.com/nickcolley/jest-axe) - Jest integration
- [Playwright accessibility testing](https://playwright.dev/docs/accessibility-testing)
@@ -563,6 +585,7 @@ const handleDelete = () => {
### Manual Testing Checklist
#### Keyboard Testing
1. [ ] Unplug mouse
2. [ ] Tab through entire page
3. [ ] All interactive elements focusable?
@@ -572,6 +595,7 @@ const handleDelete = () => {
7. [ ] Tab order logical?
#### Screen Reader Testing
1. [ ] Install NVDA (Windows) or VoiceOver (Mac)
2. [ ] Navigate page with screen reader on
3. [ ] All content announced?
@@ -580,6 +604,7 @@ const handleDelete = () => {
6. [ ] Heading hierarchy correct?
#### Contrast Testing
1. [ ] Use contrast checker on all text
2. [ ] Check UI components (buttons, borders)
3. [ ] Test in dark mode too
@@ -590,6 +615,7 @@ const handleDelete = () => {
### Testing with Real Users
**Considerations:**
- Test with actual users who rely on assistive technologies
- Different screen readers behave differently
- Mobile screen readers (VoiceOver, TalkBack) differ from desktop
@@ -600,6 +626,7 @@ const handleDelete = () => {
## Accessibility Checklist
### General
- [ ] Page has `<title>` and `<meta name="description">`
- [ ] Page has proper heading hierarchy (h1 → h2 → h3)
- [ ] Landmarks used (`<header>`, `<nav>`, `<main>`, `<footer>`)
@@ -607,12 +634,14 @@ const handleDelete = () => {
- [ ] No content relies on color alone
### Color & Contrast
- [ ] Text has 4.5:1 contrast (normal) or 3:1 (large)
- [ ] UI components have 3:1 contrast
- [ ] Tested in both light and dark modes
- [ ] Color blindness simulator used
### Keyboard
- [ ] All interactive elements focusable
- [ ] Focus indicators visible (ring, outline, etc.)
- [ ] Tab order is logical
@@ -622,6 +651,7 @@ const handleDelete = () => {
- [ ] Arrow keys navigate lists/menus
### Screen Readers
- [ ] All images have alt text
- [ ] Icon-only buttons have aria-label
- [ ] Form labels associated with inputs
@@ -631,6 +661,7 @@ const handleDelete = () => {
- [ ] ARIA roles used correctly
### Forms
- [ ] Labels associated with inputs (`htmlFor` + `id`)
- [ ] Error messages linked (`aria-describedby`)
- [ ] Invalid inputs marked (`aria-invalid`)
@@ -638,6 +669,7 @@ const handleDelete = () => {
- [ ] Submit button disabled during submission
### Focus Management
- [ ] Dialogs trap focus
- [ ] Focus returns after dialog closes
- [ ] Programmatic focus after actions
@@ -650,29 +682,36 @@ const handleDelete = () => {
**Easy improvements with big impact:**
1. **Add alt text to images**
```tsx
<img src="/logo.png" alt="Company Logo" />
```
2. **Associate labels with inputs**
```tsx
<Label htmlFor="email">Email</Label>
<Input id="email" />
```
3. **Use semantic HTML**
```tsx
<button> instead of <div onClick>
```
4. **Add aria-label to icon buttons**
```tsx
<Button aria-label="Close"><X /></Button>
<Button aria-label="Close">
<X />
</Button>
```
5. **Use semantic color tokens**
```tsx
className="text-foreground" // Auto contrast
className = 'text-foreground'; // Auto contrast
```
6. **Test with keyboard only**
@@ -691,11 +730,13 @@ const handleDelete = () => {
---
**Related Documentation:**
- [Forms](./06-forms.md) - Accessible form patterns
- [Components](./02-components.md) - All components are accessible
- [Foundations](./01-foundations.md) - Color contrast tokens
**External Resources:**
- [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/)
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
- [A11y Project Checklist](https://www.a11yproject.com/checklist/)

View File

@@ -9,19 +9,22 @@
### ALWAYS Do
1.**Import from `@/components/ui/*`**
```tsx
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
```
2. ✅ **Use semantic color tokens**
```tsx
className="bg-primary text-primary-foreground"
className="text-destructive"
className="bg-muted text-muted-foreground"
className = 'bg-primary text-primary-foreground';
className = 'text-destructive';
className = 'bg-muted text-muted-foreground';
```
3. ✅ **Use `cn()` utility for className merging**
```tsx
import { cn } from '@/lib/utils';
@@ -29,11 +32,13 @@
```
4. ✅ **Follow spacing scale** (multiples of 4: 0, 1, 2, 3, 4, 6, 8, 12, 16)
```tsx
className="p-4 space-y-6 mb-8"
className = 'p-4 space-y-6 mb-8';
```
5. ✅ **Add accessibility attributes**
```tsx
<Label htmlFor="email">Email</Label>
<Input
@@ -44,12 +49,14 @@
```
6. ✅ **Use component variants**
```tsx
<Button variant="destructive">Delete</Button>
<Alert variant="destructive">Error message</Alert>
```
7. ✅ **Compose from shadcn/ui primitives**
```tsx
// Don't create custom card components
// Use Card + CardHeader + CardTitle + CardContent
@@ -57,8 +64,8 @@
8. ✅ **Use mobile-first responsive design**
```tsx
className="text-2xl sm:text-3xl lg:text-4xl"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
className = 'text-2xl sm:text-3xl lg:text-4xl';
className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3';
```
---
@@ -66,24 +73,27 @@
### NEVER Do
1. ❌ **NO arbitrary colors**
```tsx
// ❌ WRONG
className="bg-blue-500 text-white"
className = 'bg-blue-500 text-white';
// ✅ CORRECT
className="bg-primary text-primary-foreground"
className = 'bg-primary text-primary-foreground';
```
2. ❌ **NO arbitrary spacing values**
```tsx
// ❌ WRONG
className="p-[13px] mb-[17px]"
className = 'p-[13px] mb-[17px]';
// ✅ CORRECT
className="p-4 mb-4"
className = 'p-4 mb-4';
```
3. ❌ **NO inline styles**
```tsx
// ❌ WRONG
style={{ margin: '10px', color: '#3b82f6' }}
@@ -93,6 +103,7 @@
```
4. ❌ **NO custom CSS classes** (use Tailwind utilities)
```tsx
// ❌ WRONG
<div className="my-custom-class">
@@ -102,6 +113,7 @@
```
5. ❌ **NO mixing component libraries**
```tsx
// ❌ WRONG - Don't use Material-UI, Ant Design, etc.
import { Button } from '@mui/material';
@@ -111,6 +123,7 @@
```
6. ❌ **NO skipping accessibility**
```tsx
// ❌ WRONG
<button><X /></button>
@@ -122,6 +135,7 @@
```
7. ❌ **NO creating custom variants without CVA**
```tsx
// ❌ WRONG
<Button className={type === 'danger' ? 'bg-red-500' : 'bg-blue-500'}>
@@ -138,9 +152,7 @@
```tsx
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto space-y-6">
{/* Content */}
</div>
<div className="max-w-4xl mx-auto space-y-6">{/* Content */}</div>
</div>
```
@@ -148,7 +160,9 @@
```tsx
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map(item => <Card key={item.id}>...</Card>)}
{items.map((item) => (
<Card key={item.id}>...</Card>
))}
</div>
```
@@ -160,9 +174,7 @@
<CardTitle>Form Title</CardTitle>
</CardHeader>
<CardContent>
<form className="space-y-4">
{/* Form fields */}
</form>
<form className="space-y-4">{/* Form fields */}</form>
</CardContent>
</Card>
```
@@ -170,9 +182,7 @@
### Centered Content
```tsx
<div className="max-w-2xl mx-auto px-4">
{/* Readable content width */}
</div>
<div className="max-w-2xl mx-auto px-4">{/* Readable content width */}</div>
```
---
@@ -191,17 +201,15 @@ interface MyComponentProps {
children: React.ReactNode;
}
export function MyComponent({
variant = 'default',
className,
children
}: MyComponentProps) {
export function MyComponent({ variant = 'default', className, children }: MyComponentProps) {
return (
<Card className={cn(
"p-4", // base styles
variant === 'compact' && "p-2",
className // allow overrides
)}>
<Card
className={cn(
'p-4', // base styles
variant === 'compact' && 'p-2',
className // allow overrides
)}
>
{children}
</Card>
);
@@ -215,22 +223,22 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const componentVariants = cva(
"base-classes-here", // base
'base-classes-here', // base
{
variants: {
variant: {
default: "bg-primary text-primary-foreground",
destructive: "bg-destructive text-destructive-foreground",
default: 'bg-primary text-primary-foreground',
destructive: 'bg-destructive text-destructive-foreground',
},
size: {
sm: "h-8 px-3 text-xs",
default: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base",
sm: 'h-8 px-3 text-xs',
default: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
},
},
defaultVariants: {
variant: "default",
size: "default",
variant: 'default',
size: 'default',
},
}
);
@@ -240,9 +248,7 @@ interface ComponentProps
VariantProps<typeof componentVariants> {}
export function Component({ variant, size, className, ...props }: ComponentProps) {
return (
<div className={cn(componentVariants({ variant, size, className }))} {...props} />
);
return <div className={cn(componentVariants({ variant, size, className }))} {...props} />;
}
```
@@ -313,18 +319,18 @@ export function MyForm() {
**Always use these semantic tokens:**
| Token | Usage |
|-------|-------|
| `bg-primary text-primary-foreground` | Primary buttons, CTAs |
| `bg-secondary text-secondary-foreground` | Secondary actions |
| `bg-destructive text-destructive-foreground` | Delete, errors |
| `bg-muted text-muted-foreground` | Disabled states |
| `bg-accent text-accent-foreground` | Hover states |
| `bg-card text-card-foreground` | Card backgrounds |
| `text-foreground` | Body text |
| `text-muted-foreground` | Secondary text |
| `border-border` | Borders |
| `ring-ring` | Focus rings |
| Token | Usage |
| -------------------------------------------- | --------------------- |
| `bg-primary text-primary-foreground` | Primary buttons, CTAs |
| `bg-secondary text-secondary-foreground` | Secondary actions |
| `bg-destructive text-destructive-foreground` | Delete, errors |
| `bg-muted text-muted-foreground` | Disabled states |
| `bg-accent text-accent-foreground` | Hover states |
| `bg-card text-card-foreground` | Card backgrounds |
| `text-foreground` | Body text |
| `text-muted-foreground` | Secondary text |
| `border-border` | Borders |
| `ring-ring` | Focus rings |
---
@@ -332,14 +338,14 @@ export function MyForm() {
**Use these spacing values (multiples of 4px):**
| Class | Value | Pixels | Usage |
|-------|-------|--------|-------|
| `2` | 0.5rem | 8px | Tight spacing |
| `4` | 1rem | 16px | Standard spacing |
| `6` | 1.5rem | 24px | Section spacing |
| `8` | 2rem | 32px | Large gaps |
| `12` | 3rem | 48px | Section dividers |
| `16` | 4rem | 64px | Page sections |
| Class | Value | Pixels | Usage |
| ----- | ------ | ------ | ---------------- |
| `2` | 0.5rem | 8px | Tight spacing |
| `4` | 1rem | 16px | Standard spacing |
| `6` | 1.5rem | 24px | Section spacing |
| `8` | 2rem | 32px | Large gaps |
| `12` | 3rem | 48px | Section dividers |
| `16` | 4rem | 64px | Page sections |
---
@@ -428,7 +434,7 @@ function MyCard({ title, children }) {
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>{children}</CardContent>
</Card>
</Card>;
```
### ❌ Mistake 5: Not using cn() utility
@@ -497,7 +503,7 @@ Add to `.github/copilot-instructions.md`:
```markdown
# Component Guidelines
- Import from @/components/ui/*
- Import from @/components/ui/\*
- Use semantic colors: bg-primary, text-destructive
- Spacing: multiples of 4 (p-4, mb-6, gap-8)
- Use cn() for className merging
@@ -525,20 +531,20 @@ interface DashboardCardProps {
export function DashboardCard({ title, value, trend, className }: DashboardCardProps) {
return (
<Card className={cn("p-6", className)}>
<Card className={cn('p-6', className)}>
<CardHeader>
<CardTitle className="text-sm font-medium text-muted-foreground">
{title}
</CardTitle>
<CardTitle className="text-sm font-medium text-muted-foreground">{title}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{value}</div>
{trend && (
<p className={cn(
"text-xs",
trend === 'up' && "text-green-600",
trend === 'down' && "text-destructive"
)}>
<p
className={cn(
'text-xs',
trend === 'up' && 'text-green-600',
trend === 'down' && 'text-destructive'
)}
>
{trend === 'up' ? '↑' : '↓'} Trend
</p>
)}
@@ -549,6 +555,7 @@ export function DashboardCard({ title, value, trend, className }: DashboardCardP
```
**Why it's good:**
- ✅ Imports from `@/components/ui/*`
- ✅ Uses semantic tokens
- ✅ Uses `cn()` utility

View File

@@ -20,21 +20,21 @@
### Semantic Colors
| Token | Usage | Example |
|-------|-------|---------|
| `bg-primary text-primary-foreground` | CTAs, primary actions | Primary button |
| `bg-secondary text-secondary-foreground` | Secondary actions | Secondary button |
| `bg-destructive text-destructive-foreground` | Delete, errors | Delete button, error alert |
| `bg-muted text-muted-foreground` | Disabled, subtle | Disabled button, TabsList |
| `bg-accent text-accent-foreground` | Hover states | Dropdown hover |
| `bg-card text-card-foreground` | Cards, elevated surfaces | Card component |
| `bg-popover text-popover-foreground` | Popovers, dropdowns | Dropdown content |
| `bg-background text-foreground` | Page background | Body |
| `text-foreground` | Body text | Paragraphs |
| `text-muted-foreground` | Secondary text | Captions, helper text |
| `border-border` | Borders, dividers | Card borders, separators |
| `border-input` | Input borders | Text input border |
| `ring-ring` | Focus indicators | Focus ring |
| Token | Usage | Example |
| -------------------------------------------- | ------------------------ | -------------------------- |
| `bg-primary text-primary-foreground` | CTAs, primary actions | Primary button |
| `bg-secondary text-secondary-foreground` | Secondary actions | Secondary button |
| `bg-destructive text-destructive-foreground` | Delete, errors | Delete button, error alert |
| `bg-muted text-muted-foreground` | Disabled, subtle | Disabled button, TabsList |
| `bg-accent text-accent-foreground` | Hover states | Dropdown hover |
| `bg-card text-card-foreground` | Cards, elevated surfaces | Card component |
| `bg-popover text-popover-foreground` | Popovers, dropdowns | Dropdown content |
| `bg-background text-foreground` | Page background | Body |
| `text-foreground` | Body text | Paragraphs |
| `text-muted-foreground` | Secondary text | Captions, helper text |
| `border-border` | Borders, dividers | Card borders, separators |
| `border-input` | Input borders | Text input border |
| `ring-ring` | Focus indicators | Focus ring |
### Usage Examples
@@ -61,29 +61,29 @@
### Font Sizes
| Class | rem | px | Use Case | Common |
|-------|-----|----|----|:------:|
| `text-xs` | 0.75rem | 12px | Labels, fine print | |
| `text-sm` | 0.875rem | 14px | Secondary text, captions | |
| `text-base` | 1rem | 16px | Body text (default) | ⭐ |
| `text-lg` | 1.125rem | 18px | Subheadings | |
| `text-xl` | 1.25rem | 20px | Card titles | ⭐ |
| `text-2xl` | 1.5rem | 24px | Section headings | ⭐ |
| `text-3xl` | 1.875rem | 30px | Page titles | ⭐ |
| `text-4xl` | 2.25rem | 36px | Large headings | |
| `text-5xl` | 3rem | 48px | Hero text | |
| Class | rem | px | Use Case | Common |
| ----------- | -------- | ---- | ------------------------ | :----: |
| `text-xs` | 0.75rem | 12px | Labels, fine print | |
| `text-sm` | 0.875rem | 14px | Secondary text, captions | |
| `text-base` | 1rem | 16px | Body text (default) | ⭐ |
| `text-lg` | 1.125rem | 18px | Subheadings | |
| `text-xl` | 1.25rem | 20px | Card titles | ⭐ |
| `text-2xl` | 1.5rem | 24px | Section headings | ⭐ |
| `text-3xl` | 1.875rem | 30px | Page titles | ⭐ |
| `text-4xl` | 2.25rem | 36px | Large headings | |
| `text-5xl` | 3rem | 48px | Hero text | |
⭐ = Most commonly used
### Font Weights
| Class | Value | Use Case | Common |
|-------|-------|----------|:------:|
| `font-light` | 300 | De-emphasized text | |
| `font-normal` | 400 | Body text (default) | ⭐ |
| `font-medium` | 500 | Labels, menu items | ⭐ |
| `font-semibold` | 600 | Subheadings, buttons | |
| `font-bold` | 700 | Headings, emphasis | ⭐ |
| Class | Value | Use Case | Common |
| --------------- | ----- | -------------------- | :----: |
| `font-light` | 300 | De-emphasized text | |
| `font-normal` | 400 | Body text (default) | ⭐ |
| `font-medium` | 500 | Labels, menu items | ⭐ |
| `font-semibold` | 600 | Subheadings, buttons | |
| `font-bold` | 700 | Headings, emphasis | ⭐ |
⭐ = Most commonly used
@@ -115,35 +115,35 @@
### Spacing Values
| Token | rem | px | Use Case | Common |
|-------|-----|----|----|:------:|
| `0` | 0 | 0px | No spacing | |
| `px` | - | 1px | Borders | |
| `0.5` | 0.125rem | 2px | Very tight | |
| `1` | 0.25rem | 4px | Icon gaps | |
| `2` | 0.5rem | 8px | Tight spacing (label → input) | ⭐ |
| `3` | 0.75rem | 12px | Component padding | |
| `4` | 1rem | 16px | Standard spacing (form fields) | |
| `5` | 1.25rem | 20px | Medium spacing | |
| `6` | 1.5rem | 24px | Section spacing (cards) | ⭐ |
| `8` | 2rem | 32px | Large gaps | ⭐ |
| `10` | 2.5rem | 40px | Very large gaps | |
| `12` | 3rem | 48px | Section dividers | ⭐ |
| `16` | 4rem | 64px | Page sections | |
| Token | rem | px | Use Case | Common |
| ----- | -------- | ---- | ------------------------------ | :----: |
| `0` | 0 | 0px | No spacing | |
| `px` | - | 1px | Borders | |
| `0.5` | 0.125rem | 2px | Very tight | |
| `1` | 0.25rem | 4px | Icon gaps | |
| `2` | 0.5rem | 8px | Tight spacing (label → input) | ⭐ |
| `3` | 0.75rem | 12px | Component padding | |
| `4` | 1rem | 16px | Standard spacing (form fields) | |
| `5` | 1.25rem | 20px | Medium spacing | |
| `6` | 1.5rem | 24px | Section spacing (cards) | ⭐ |
| `8` | 2rem | 32px | Large gaps | ⭐ |
| `10` | 2.5rem | 40px | Very large gaps | |
| `12` | 3rem | 48px | Section dividers | ⭐ |
| `16` | 4rem | 64px | Page sections | |
⭐ = Most commonly used
### Spacing Methods
| Method | Use Case | Example |
|--------|----------|---------|
| `gap-4` | Flex/grid spacing | `flex gap-4` |
| `space-y-4` | Vertical stack spacing | `space-y-4` |
| `space-x-4` | Horizontal stack spacing | `space-x-4` |
| `p-4` | Padding (all sides) | `p-4` |
| `px-4` | Horizontal padding | `px-4` |
| `py-4` | Vertical padding | `py-4` |
| `m-4` | Margin (exceptions only!) | `mt-8` |
| Method | Use Case | Example |
| ----------- | ------------------------- | ------------ |
| `gap-4` | Flex/grid spacing | `flex gap-4` |
| `space-y-4` | Vertical stack spacing | `space-y-4` |
| `space-x-4` | Horizontal stack spacing | `space-x-4` |
| `p-4` | Padding (all sides) | `p-4` |
| `px-4` | Horizontal padding | `px-4` |
| `py-4` | Vertical padding | `py-4` |
| `m-4` | Margin (exceptions only!) | `mt-8` |
### Common Spacing Patterns
@@ -217,52 +217,52 @@
```tsx
// 1 → 2 → 3 progression (most common)
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6';
// 1 → 2 → 4 progression
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"
className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6';
// 1 → 2 progression (simple)
className="grid grid-cols-1 md:grid-cols-2 gap-6"
className = 'grid grid-cols-1 md:grid-cols-2 gap-6';
// 1 → 3 progression (skip 2)
className="grid grid-cols-1 lg:grid-cols-3 gap-6"
className = 'grid grid-cols-1 lg:grid-cols-3 gap-6';
```
### Container Widths
```tsx
// Standard container
className="container mx-auto px-4"
className = 'container mx-auto px-4';
// Constrained widths
className="max-w-md mx-auto" // 448px - Forms
className="max-w-lg mx-auto" // 512px - Modals
className="max-w-2xl mx-auto" // 672px - Articles
className="max-w-4xl mx-auto" // 896px - Wide layouts
className="max-w-7xl mx-auto" // 1280px - Full page
className = 'max-w-md mx-auto'; // 448px - Forms
className = 'max-w-lg mx-auto'; // 512px - Modals
className = 'max-w-2xl mx-auto'; // 672px - Articles
className = 'max-w-4xl mx-auto'; // 896px - Wide layouts
className = 'max-w-7xl mx-auto'; // 1280px - Full page
```
### Flex Patterns
```tsx
// Horizontal flex
className="flex gap-4"
className = 'flex gap-4';
// Vertical flex
className="flex flex-col gap-4"
className = 'flex flex-col gap-4';
// Center items
className="flex items-center justify-center"
className = 'flex items-center justify-center';
// Space between
className="flex items-center justify-between"
className = 'flex items-center justify-between';
// Wrap items
className="flex flex-wrap gap-4"
className = 'flex flex-wrap gap-4';
// Responsive: stack on mobile, row on desktop
className="flex flex-col sm:flex-row gap-4"
className = 'flex flex-col sm:flex-row gap-4';
```
---
@@ -273,9 +273,7 @@ className="flex flex-col sm:flex-row gap-4"
```tsx
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto space-y-6">
{/* Content */}
</div>
<div className="max-w-4xl mx-auto space-y-6">{/* Content */}</div>
</div>
```
@@ -287,7 +285,9 @@ className="flex flex-col sm:flex-row gap-4"
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
</div>
<Button variant="outline" size="sm">Action</Button>
<Button variant="outline" size="sm">
Action
</Button>
</CardHeader>
```
@@ -319,9 +319,7 @@ className="flex flex-col sm:flex-row gap-4"
<CardTitle>Form Title</CardTitle>
</CardHeader>
<CardContent>
<form className="space-y-4">
{/* Fields */}
</form>
<form className="space-y-4">{/* Fields */}</form>
</CardContent>
</Card>
</div>
@@ -354,17 +352,13 @@ className="flex flex-col sm:flex-row gap-4"
### Responsive Text
```tsx
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-bold">
Responsive Title
</h1>
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-bold">Responsive Title</h1>
```
### Responsive Padding
```tsx
<div className="p-4 sm:p-6 lg:p-8">
Responsive padding
</div>
<div className="p-4 sm:p-6 lg:p-8">Responsive padding</div>
```
---
@@ -427,16 +421,16 @@ Has error?
## Keyboard Shortcuts
| Key | Action | Context |
|-----|--------|---------|
| `Tab` | Move focus forward | All |
| `Shift + Tab` | Move focus backward | All |
| `Enter` | Activate button/link | Buttons, links |
| `Space` | Activate button/checkbox | Buttons, checkboxes |
| `Escape` | Close overlay | Dialogs, dropdowns |
| `Arrow keys` | Navigate items | Dropdowns, lists |
| `Home` | Jump to start | Lists |
| `End` | Jump to end | Lists |
| Key | Action | Context |
| ------------- | ------------------------ | ------------------- |
| `Tab` | Move focus forward | All |
| `Shift + Tab` | Move focus backward | All |
| `Enter` | Activate button/link | Buttons, links |
| `Space` | Activate button/checkbox | Buttons, checkboxes |
| `Escape` | Close overlay | Dialogs, dropdowns |
| `Arrow keys` | Navigate items | Dropdowns, lists |
| `Home` | Jump to start | Lists |
| `End` | Jump to end | Lists |
---
@@ -482,7 +476,13 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
// Utilities
@@ -506,53 +506,53 @@ import { Check, X, AlertCircle, Loader2 } from 'lucide-react';
```tsx
// Required string
z.string().min(1, 'Required')
z.string().min(1, 'Required');
// Email
z.string().email('Invalid email')
z.string().email('Invalid email');
// Min/max length
z.string().min(8, 'Min 8 chars').max(100, 'Max 100 chars')
z.string().min(8, 'Min 8 chars').max(100, 'Max 100 chars');
// Optional
z.string().optional()
z.string().optional();
// Number
z.coerce.number().min(0).max(100)
z.coerce.number().min(0).max(100);
// Enum
z.enum(['admin', 'user', 'guest'])
z.enum(['admin', 'user', 'guest']);
// Boolean
z.boolean().refine(val => val === true, { message: 'Must accept' })
z.boolean().refine((val) => val === true, { message: 'Must accept' });
// Password confirmation
z.object({
password: z.string().min(8),
confirmPassword: z.string()
}).refine(data => data.password === data.confirmPassword, {
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
})
path: ['confirmPassword'],
});
```
---
## Responsive Breakpoints
| Breakpoint | Min Width | Typical Device |
|------------|-----------|----------------|
| `sm:` | 640px | Large phones, small tablets |
| `md:` | 768px | Tablets |
| `lg:` | 1024px | Laptops, desktops |
| `xl:` | 1280px | Large desktops |
| `2xl:` | 1536px | Extra large screens |
| Breakpoint | Min Width | Typical Device |
| ---------- | --------- | --------------------------- |
| `sm:` | 640px | Large phones, small tablets |
| `md:` | 768px | Tablets |
| `lg:` | 1024px | Laptops, desktops |
| `xl:` | 1280px | Large desktops |
| `2xl:` | 1536px | Extra large screens |
```tsx
// Mobile-first (default → sm → md → lg)
className="text-sm sm:text-base md:text-lg lg:text-xl"
className="p-4 sm:p-6 lg:p-8"
className="grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
className = 'text-sm sm:text-base md:text-lg lg:text-xl';
className = 'p-4 sm:p-6 lg:p-8';
className = 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3';
```
---
@@ -562,20 +562,20 @@ className="grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
### Shadows
```tsx
shadow-sm // Cards, panels
shadow-md // Dropdowns, tooltips
shadow-lg // Modals, popovers
shadow-xl // Floating notifications
shadow - sm; // Cards, panels
shadow - md; // Dropdowns, tooltips
shadow - lg; // Modals, popovers
shadow - xl; // Floating notifications
```
### Border Radius
```tsx
rounded-sm // 2px - Tags, small badges
rounded-md // 4px - Inputs, small buttons
rounded-lg // 6px - Cards, buttons (default)
rounded-xl // 10px - Large cards, modals
rounded-full // Pills, avatars, icon buttons
rounded - sm; // 2px - Tags, small badges
rounded - md; // 4px - Inputs, small buttons
rounded - lg; // 6px - Cards, buttons (default)
rounded - xl; // 10px - Large cards, modals
rounded - full; // Pills, avatars, icon buttons
```
---
@@ -589,6 +589,7 @@ rounded-full // Pills, avatars, icon buttons
---
**Related Documentation:**
- [Quick Start](./00-quick-start.md) - 5-minute crash course
- [Foundations](./01-foundations.md) - Detailed color, typography, spacing
- [Components](./02-components.md) - All component variants

View File

@@ -10,7 +10,9 @@
## Project Overview
### Vision
Create a world-class design system documentation that:
- Follows Pareto principle (80% coverage with 20% content)
- Includes AI-specific code generation guidelines
- Provides interactive, copy-paste examples
@@ -18,6 +20,7 @@ Create a world-class design system documentation that:
- Maintains perfect internal coherence and link integrity
### Key Principles
1. **Pareto-Efficiency** - 80/20 rule applied throughout
2. **AI-Optimized** - Dedicated guidelines for AI code generation
3. **Interconnected** - All docs cross-reference each other
@@ -163,6 +166,7 @@ Create a world-class design system documentation that:
### Documentation Review & Fixes ✅
#### Issues Found During Review:
1. **Time estimates in section headers** - Removed all (user request)
- Removed "⏱️ Time to productive: 5 minutes" from header
- Removed "(3 minutes)", "(30 seconds)" from all section headers
@@ -178,6 +182,7 @@ Create a world-class design system documentation that:
- Fixed: Added missing `SelectGroup` and `SelectLabel` to import statement in 02-components.md
#### Comprehensive Review Results:
- **✅ 100+ links checked**
- **✅ 0 broken internal doc links**
- **✅ 0 logic inconsistencies**
@@ -200,7 +205,9 @@ Create a world-class design system documentation that:
## Phase 2: Interactive Demos (PENDING)
### Objective
Create live, interactive demonstration pages at `/dev/*` routes with:
- Copy-paste ready code snippets
- Before/after comparisons
- Live component examples
@@ -272,12 +279,14 @@ Create live, interactive demonstration pages at `/dev/*` routes with:
### Overall Progress: 100% Complete ✅
**Phase 1: Documentation** ✅ 100% (14/14 tasks)
- All documentation files created (~7,600 lines)
- All issues fixed (4 issues resolved)
- Comprehensive review completed (100+ links verified)
- CLAUDE.md updated
**Phase 2: Interactive Demos** ✅ 100% (6/6 tasks)
- Utility components created (~470 lines)
- Hub page created (~220 lines)
- All demo pages created and enhanced (~2,388 lines)
@@ -386,6 +395,7 @@ Create live, interactive demonstration pages at `/dev/*` routes with:
### Technical Implementation
**Technologies Used:**
- Next.js 15 App Router
- React 19 + TypeScript
- shadcn/ui components (all)
@@ -395,6 +405,7 @@ Create live, interactive demonstration pages at `/dev/*` routes with:
- Responsive design (mobile-first)
**Architecture:**
- Server components for static pages (hub, layouts, spacing)
- Client components for interactive pages (components, forms)
- Reusable utility components in `/src/components/dev/`

View File

@@ -6,26 +6,28 @@
## 🚀 Quick Navigation
| For... | Start Here | Time |
|--------|-----------|------|
| **Quick Start** | [⚡ 5-Minute Crash Course](./00-quick-start.md) | 5 min |
| **Component Development** | [🧩 Components](./02-components.md) → [🔨 Creation Guide](./05-component-creation.md) | 15 min |
| **Layout Design** | [📐 Layouts](./03-layouts.md) → [📏 Spacing](./04-spacing-philosophy.md) | 20 min |
| **AI Code Generation** | [🤖 AI Guidelines](./08-ai-guidelines.md) | 3 min |
| **Quick Reference** | [📚 Reference Tables](./99-reference.md) | Instant |
| **Complete Guide** | Read all docs in order | 1 hour |
| For... | Start Here | Time |
| ------------------------- | ------------------------------------------------------------------------------------- | ------- |
| **Quick Start** | [⚡ 5-Minute Crash Course](./00-quick-start.md) | 5 min |
| **Component Development** | [🧩 Components](./02-components.md) → [🔨 Creation Guide](./05-component-creation.md) | 15 min |
| **Layout Design** | [📐 Layouts](./03-layouts.md) → [📏 Spacing](./04-spacing-philosophy.md) | 20 min |
| **AI Code Generation** | [🤖 AI Guidelines](./08-ai-guidelines.md) | 3 min |
| **Quick Reference** | [📚 Reference Tables](./99-reference.md) | Instant |
| **Complete Guide** | Read all docs in order | 1 hour |
---
## 📖 Documentation Structure
### Getting Started
- **[00. Quick Start](./00-quick-start.md)** ⚡
- 5-minute crash course
- Essential components and patterns
- Copy-paste ready examples
### Fundamentals
- **[01. Foundations](./01-foundations.md)** 🎨
- Color system (OKLCH)
- Typography scale
@@ -39,6 +41,7 @@
- Composition patterns
### Layouts & Spacing
- **[03. Layouts](./03-layouts.md)** 📐
- Grid vs Flex decision tree
- Common layout patterns
@@ -52,6 +55,7 @@
- Consistency patterns
### Building Components
- **[05. Component Creation](./05-component-creation.md)** 🔨
- When to create vs compose
- Component templates
@@ -65,6 +69,7 @@
- Multi-field examples
### Best Practices
- **[07. Accessibility](./07-accessibility.md)** ♿
- WCAG AA compliance
- Keyboard navigation
@@ -78,6 +83,7 @@
- Component templates
### Reference
- **[99. Reference Tables](./99-reference.md)** 📚
- Quick lookup tables
- All tokens at a glance
@@ -95,6 +101,7 @@ Explore live examples and copy-paste code:
- **[Form Patterns](/dev/forms)** - Complete form examples
Each demo page includes:
- ✅ Live, interactive examples
- ✅ Click-to-copy code snippets
- ✅ Before/after comparisons
@@ -105,6 +112,7 @@ Each demo page includes:
## 🛤️ Learning Paths
### Path 1: Speedrun (5 minutes)
**Goal**: Start building immediately
1. [Quick Start](./00-quick-start.md) - Essential patterns
@@ -116,6 +124,7 @@ Each demo page includes:
---
### Path 2: Component Developer (15 minutes)
**Goal**: Master component building
1. [Quick Start](./00-quick-start.md) - Basics
@@ -128,6 +137,7 @@ Each demo page includes:
---
### Path 3: Layout Specialist (20 minutes)
**Goal**: Master layouts and spacing
1. [Quick Start](./00-quick-start.md) - Basics
@@ -141,6 +151,7 @@ Each demo page includes:
---
### Path 4: Form Specialist (15 minutes)
**Goal**: Master forms and validation
1. [Quick Start](./00-quick-start.md) - Basics
@@ -154,6 +165,7 @@ Each demo page includes:
---
### Path 5: AI Setup (3 minutes)
**Goal**: Configure AI for perfect code generation
1. [AI Guidelines](./08-ai-guidelines.md) - Read once, code forever
@@ -164,9 +176,11 @@ Each demo page includes:
---
### Path 6: Comprehensive Mastery (1 hour)
**Goal**: Complete understanding of the design system
Read all documents in order:
1. [Quick Start](./00-quick-start.md)
2. [Foundations](./01-foundations.md)
3. [Components](./02-components.md)
@@ -211,18 +225,21 @@ Our design system is built on these core principles:
## 🤝 Contributing to the Design System
### Adding a New Component
1. Read [Component Creation Guide](./05-component-creation.md)
2. Follow the template
3. Add to [Component Showcase](/dev/components)
4. Document in [Components](./02-components.md)
### Adding a New Pattern
1. Validate it solves a real need (used 3+ times)
2. Document in appropriate guide
3. Add to [Reference](./99-reference.md)
4. Create example in `/dev/`
### Updating Colors/Tokens
1. Edit `src/app/globals.css`
2. Test in both light and dark modes
3. Verify WCAG AA contrast