Initial commit
This commit is contained in:
@@ -0,0 +1,484 @@
|
||||
# Accessibility Checklist for Tailwind + shadcn/ui Setup
|
||||
|
||||
## Overview
|
||||
|
||||
This checklist ensures the Tailwind + shadcn/ui setup meets WCAG 2.1 Level AA standards and provides an inclusive user experience.
|
||||
|
||||
## Color & Contrast
|
||||
|
||||
### Requirements
|
||||
- [OK] Normal text (< 18pt): Contrast ratio ≥ 4.5:1
|
||||
- [OK] Large text (≥ 18pt or bold 14pt): Contrast ratio ≥ 3:1
|
||||
- [OK] UI components: Contrast ratio ≥ 3:1
|
||||
- [OK] Focus indicators: Contrast ratio ≥ 3:1
|
||||
|
||||
### Implementation
|
||||
|
||||
```css
|
||||
/* Light mode - High contrast */
|
||||
:root {
|
||||
--background: 0 0% 100%; /* White */
|
||||
--foreground: 222.2 84% 4.9%; /* Near black - 16.7:1 ratio */
|
||||
--muted: 210 40% 96.1%; /* Light gray background */
|
||||
--muted-foreground: 215.4 16.3% 46.9%; /* Medium gray text - 4.6:1 ratio */
|
||||
--border: 214.3 31.8% 91.4%; /* Light border */
|
||||
}
|
||||
|
||||
/* Dark mode - High contrast */
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%; /* Near black */
|
||||
--foreground: 210 40% 98%; /* Near white - 16.5:1 ratio */
|
||||
--muted: 217.2 32.6% 17.5%; /* Dark gray background */
|
||||
--muted-foreground: 215 20.2% 65.1%; /* Light gray text - 6.8:1 ratio */
|
||||
--border: 217.2 32.6% 17.5%; /* Dark border */
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Contrast
|
||||
|
||||
```bash
|
||||
# Use online tools:
|
||||
# - https://webaim.org/resources/contrastchecker/
|
||||
# - https://contrast-ratio.com/
|
||||
# - Chrome DevTools (Lighthouse audit)
|
||||
|
||||
# Or programmatically:
|
||||
npm install --save-dev axe-core @axe-core/playwright
|
||||
```
|
||||
|
||||
## Focus Management
|
||||
|
||||
### Visible Focus Indicators
|
||||
|
||||
```css
|
||||
/* Global focus styles */
|
||||
@layer base {
|
||||
*:focus-visible {
|
||||
@apply outline-none ring-2 ring-ring ring-offset-2 ring-offset-background;
|
||||
}
|
||||
|
||||
/* Respect reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*:focus-visible {
|
||||
@apply transition-none;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Focus Order
|
||||
- [OK] Logical tab order (follows visual order)
|
||||
- [OK] No keyboard traps
|
||||
- [OK] Skip links for navigation
|
||||
- [OK] Focus moves appropriately in modals/dialogs
|
||||
|
||||
### Implementation
|
||||
|
||||
```tsx
|
||||
// Skip link (in layout)
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50"
|
||||
>
|
||||
Skip to main content
|
||||
</a>
|
||||
|
||||
{/* Rest of layout */}
|
||||
<main id="main-content">
|
||||
{children}
|
||||
</main>
|
||||
```
|
||||
|
||||
## Keyboard Navigation
|
||||
|
||||
### Requirements
|
||||
- [OK] All interactive elements keyboard accessible
|
||||
- [OK] Logical tab order
|
||||
- [OK] Keyboard shortcuts don't conflict
|
||||
- [OK] Escape closes modals/dropdowns
|
||||
- [OK] Arrow keys for menus/lists
|
||||
- [OK] Enter/Space activates buttons
|
||||
|
||||
### shadcn/ui Components
|
||||
|
||||
All shadcn/ui components support keyboard navigation out of the box:
|
||||
|
||||
```tsx
|
||||
// Dialog - auto-handles:
|
||||
// - ESC to close
|
||||
// - Focus trap
|
||||
// - Return focus on close
|
||||
<Dialog>
|
||||
<DialogContent>
|
||||
<DialogTitle>Title</DialogTitle>
|
||||
{/* Content */}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
// Dropdown - auto-handles:
|
||||
// - Arrow keys for navigation
|
||||
// - Enter to select
|
||||
// - ESC to close
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>Menu</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>Item 1</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
```
|
||||
|
||||
## Semantic HTML
|
||||
|
||||
### Use Proper Elements
|
||||
|
||||
```tsx
|
||||
[OK] Good - Semantic
|
||||
<nav aria-label="Main navigation">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1>Article Title</h1>
|
||||
<p>Content...</p>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Company</p>
|
||||
</footer>
|
||||
|
||||
[ERROR] Bad - Non-semantic
|
||||
<div className="nav">
|
||||
<div className="link" onClick={goHome}>Home</div>
|
||||
</div>
|
||||
|
||||
<div className="main">
|
||||
<div className="article">
|
||||
<div className="title">Article Title</div>
|
||||
<div>Content...</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Heading Hierarchy
|
||||
|
||||
```tsx
|
||||
[OK] Good - Logical hierarchy
|
||||
<h1>Page Title</h1>
|
||||
<h2>Section</h2>
|
||||
<h3>Subsection</h3>
|
||||
<h2>Another Section</h2>
|
||||
|
||||
[ERROR] Bad - Skips levels
|
||||
<h1>Page Title</h1>
|
||||
<h3>Section</h3> {/* Skipped h2 */}
|
||||
<h2>Another Section</h2> {/* Out of order */}
|
||||
```
|
||||
|
||||
## Form Accessibility
|
||||
|
||||
### Always Pair Labels with Inputs
|
||||
|
||||
```tsx
|
||||
[OK] Good
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email address</Label>
|
||||
<Input id="email" type="email" required aria-required="true" />
|
||||
</div>
|
||||
|
||||
[ERROR] Bad
|
||||
<Input placeholder="Email address" /> {/* Placeholder is not a label */}
|
||||
```
|
||||
|
||||
### Error Messages
|
||||
|
||||
```tsx
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
aria-invalid={!!errors.password}
|
||||
aria-describedby={errors.password ? "password-error" : undefined}
|
||||
/>
|
||||
{errors.password && (
|
||||
<p id="password-error" className="text-sm text-destructive" role="alert">
|
||||
{errors.password.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Required Fields
|
||||
|
||||
```tsx
|
||||
<Label htmlFor="username">
|
||||
Username
|
||||
<span className="text-destructive" aria-label="required">*</span>
|
||||
</Label>
|
||||
<Input id="username" required aria-required="true" />
|
||||
```
|
||||
|
||||
## ARIA Attributes
|
||||
|
||||
### When to Use ARIA
|
||||
|
||||
**First Rule**: Don't use ARIA unless necessary. Use semantic HTML first.
|
||||
|
||||
```tsx
|
||||
[OK] Good - Semantic HTML (no ARIA needed)
|
||||
<button>Click me</button>
|
||||
|
||||
[ERROR] Unnecessary ARIA
|
||||
<div role="button" tabIndex={0} onClick={...}>Click me</div>
|
||||
```
|
||||
|
||||
### Common ARIA Patterns
|
||||
|
||||
```tsx
|
||||
// Live regions for dynamic content
|
||||
<div role="status" aria-live="polite" aria-atomic="true">
|
||||
{statusMessage}
|
||||
</div>
|
||||
|
||||
// Loading state
|
||||
<Button disabled={isLoading} aria-busy={isLoading}>
|
||||
{isLoading ? "Loading..." : "Submit"}
|
||||
</Button>
|
||||
|
||||
// Icon buttons need labels
|
||||
<Button size="icon" aria-label="Close menu">
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
// Expanded/collapsed state
|
||||
<Button
|
||||
aria-expanded={isOpen}
|
||||
aria-controls="content-id"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
Toggle
|
||||
</Button>
|
||||
<div id="content-id" hidden={!isOpen}>
|
||||
Content
|
||||
</div>
|
||||
```
|
||||
|
||||
## Screen Reader Support
|
||||
|
||||
### Visually Hidden Content
|
||||
|
||||
Use `.sr-only` for screen-reader-only text:
|
||||
|
||||
```tsx
|
||||
<Button>
|
||||
<span className="sr-only">Delete item</span>
|
||||
<Trash className="h-4 w-4" aria-hidden="true" />
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Skip Links
|
||||
|
||||
```tsx
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-background focus:ring-2"
|
||||
>
|
||||
Skip to main content
|
||||
</a>
|
||||
```
|
||||
|
||||
### Icon-Only Buttons
|
||||
|
||||
```tsx
|
||||
// Always provide text alternative
|
||||
<Button variant="ghost" size="icon" aria-label="Search">
|
||||
<Search className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
// Or use tooltip with title
|
||||
<Button variant="ghost" size="icon" title="Search">
|
||||
<Search className="h-4 w-4" />
|
||||
<span className="sr-only">Search</span>
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Motion & Animation
|
||||
|
||||
### Respect User Preferences
|
||||
|
||||
```css
|
||||
/* Disable animations for users who prefer reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation in Components
|
||||
|
||||
```tsx
|
||||
// Add to globals.css
|
||||
@layer base {
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dark Mode Accessibility
|
||||
|
||||
### Proper Contrast in Both Modes
|
||||
|
||||
```css
|
||||
/* Test both themes */
|
||||
:root {
|
||||
/* Light mode */
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
/* Ensure ≥ 4.5:1 ratio */
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Dark mode */
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
/* Ensure ≥ 4.5:1 ratio */
|
||||
}
|
||||
```
|
||||
|
||||
### Flash Prevention
|
||||
|
||||
```tsx
|
||||
// In app/layout.tsx
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange {/* Prevent flash */}
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
### Manual Testing
|
||||
|
||||
- [ ] Navigate entire app with keyboard only (no mouse)
|
||||
- [ ] Test with screen reader (NVDA, JAWS, VoiceOver)
|
||||
- [ ] Zoom to 200% and ensure layout doesn't break
|
||||
- [ ] Test in high contrast mode
|
||||
- [ ] Verify dark mode contrast
|
||||
- [ ] Check focus indicators on all interactive elements
|
||||
- [ ] Test form validation with screen reader
|
||||
|
||||
### Automated Testing
|
||||
|
||||
```bash
|
||||
# Install axe-core
|
||||
npm install --save-dev @axe-core/playwright
|
||||
|
||||
# Use in tests
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { injectAxe, checkA11y } from 'axe-playwright'
|
||||
|
||||
test('homepage is accessible', async ({ page }) => {
|
||||
await page.goto('http://localhost:3000')
|
||||
await injectAxe(page)
|
||||
await checkA11y(page)
|
||||
})
|
||||
```
|
||||
|
||||
### Browser DevTools
|
||||
|
||||
- Chrome Lighthouse (Accessibility audit)
|
||||
- Firefox Accessibility Inspector
|
||||
- Edge Accessibility Insights
|
||||
- axe DevTools Extension
|
||||
|
||||
## Common Issues & Fixes
|
||||
|
||||
### Issue: Missing Form Labels
|
||||
|
||||
```tsx
|
||||
[ERROR] Problem
|
||||
<Input placeholder="Email" />
|
||||
|
||||
[OK] Fix
|
||||
<div>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input id="email" placeholder="you@example.com" />
|
||||
</div>
|
||||
```
|
||||
|
||||
### Issue: Non-Accessible Custom Components
|
||||
|
||||
```tsx
|
||||
[ERROR] Problem
|
||||
<div onClick={handleClick} className="cursor-pointer">
|
||||
Click me
|
||||
</div>
|
||||
|
||||
[OK] Fix
|
||||
<button onClick={handleClick} type="button">
|
||||
Click me
|
||||
</button>
|
||||
```
|
||||
|
||||
### Issue: Low Color Contrast
|
||||
|
||||
```tsx
|
||||
[ERROR] Problem
|
||||
<p className="text-gray-400">Important text</p> {/* 2.8:1 ratio */}
|
||||
|
||||
[OK] Fix
|
||||
<p className="text-gray-700 dark:text-gray-300">Important text</p> {/* 5.2:1 ratio */}
|
||||
```
|
||||
|
||||
### Issue: Missing Focus Indicators
|
||||
|
||||
```tsx
|
||||
[ERROR] Problem
|
||||
<Button className="focus:outline-none">Click</Button>
|
||||
|
||||
[OK] Fix
|
||||
<Button className="focus-visible:ring-2 focus-visible:ring-ring">Click</Button>
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **WCAG Guidelines**: https://www.w3.org/WAI/WCAG21/quickref/
|
||||
- **WebAIM**: https://webaim.org/
|
||||
- **a11y Project**: https://www.a11yproject.com/
|
||||
- **Radix UI (shadcn base)**: https://radix-ui.com (includes A11y)
|
||||
- **MDN Accessibility**: https://developer.mozilla.org/en-US/docs/Web/Accessibility
|
||||
|
||||
## Skill Implementation
|
||||
|
||||
This skill ensures:
|
||||
- [OK] High contrast color tokens (≥ 4.5:1 for text)
|
||||
- [OK] Visible focus styles with ring utilities
|
||||
- [OK] Skip link in base layout
|
||||
- [OK] Semantic HTML landmarks (header, nav, main, footer)
|
||||
- [OK] Proper label/input associations
|
||||
- [OK] Dark mode with accessible contrast
|
||||
- [OK] Reduced motion support
|
||||
- [OK] All shadcn components use Radix (accessible primitives)
|
||||
@@ -0,0 +1,370 @@
|
||||
# shadcn/ui Component Reference
|
||||
|
||||
## Overview
|
||||
|
||||
shadcn/ui provides a collection of re-usable components built with Radix UI and Tailwind CSS. Components are NOT installed as dependencies but copied into your project, giving you full control.
|
||||
|
||||
## Installation Command
|
||||
|
||||
```bash
|
||||
npx shadcn-ui@latest add [component-name]
|
||||
```
|
||||
|
||||
## Baseline Components for This Skill
|
||||
|
||||
The following components are installed by default:
|
||||
|
||||
### Core UI Elements
|
||||
- **button**: Primary interactive element with variants
|
||||
- **card**: Container for grouped content
|
||||
- **input**: Text input field
|
||||
- **label**: Form label with accessibility support
|
||||
- **dialog**: Modal dialog/overlay
|
||||
- **separator**: Visual or semantic separator
|
||||
|
||||
### Layout & Navigation
|
||||
- **sheet**: Slide-over panel (mobile sidebar)
|
||||
- **dropdown-menu**: Contextual menu with submenus
|
||||
- **navigation-menu**: Main navigation component
|
||||
|
||||
### Feedback & Notifications
|
||||
- **toast**: Temporary notifications (via Sonner)
|
||||
- **alert**: Static notification messages
|
||||
- **badge**: Status or category indicator
|
||||
|
||||
## Available Components by Category
|
||||
|
||||
### Form Components
|
||||
- `checkbox` - Checkbox input with label
|
||||
- `input` - Text input field
|
||||
- `label` - Form label
|
||||
- `textarea` - Multi-line text input
|
||||
- `select` - Dropdown select
|
||||
- `radio-group` - Radio button group
|
||||
- `switch` - Toggle switch
|
||||
- `slider` - Range slider
|
||||
- `form` - Form wrapper with React Hook Form integration
|
||||
- `combobox` - Searchable select (autocomplete)
|
||||
- `date-picker` - Date selection with calendar
|
||||
- `calendar` - Calendar component
|
||||
|
||||
### Layout Components
|
||||
- `card` - Content card with header/footer
|
||||
- `sheet` - Slide-over panel
|
||||
- `dialog` - Modal dialog
|
||||
- `popover` - Floating popover
|
||||
- `drawer` - Bottom drawer (mobile)
|
||||
- `separator` - Divider line
|
||||
- `scroll-area` - Custom scrollable area
|
||||
- `aspect-ratio` - Maintain aspect ratio
|
||||
- `resizable` - Resizable panels
|
||||
|
||||
### Navigation Components
|
||||
- `navigation-menu` - Main navigation
|
||||
- `menubar` - Desktop menu bar
|
||||
- `dropdown-menu` - Context/dropdown menu
|
||||
- `context-menu` - Right-click context menu
|
||||
- `tabs` - Tab navigation
|
||||
- `breadcrumb` - Breadcrumb navigation
|
||||
- `pagination` - Page navigation
|
||||
- `command` - Command palette (⌘K)
|
||||
|
||||
### Feedback Components
|
||||
- `toast` - Toast notifications
|
||||
- `alert` - Alert messages
|
||||
- `alert-dialog` - Confirmation dialog
|
||||
- `badge` - Status badge
|
||||
- `progress` - Progress indicator
|
||||
- `skeleton` - Loading skeleton
|
||||
- `spinner` - Loading spinner
|
||||
|
||||
### Data Display
|
||||
- `table` - Data table
|
||||
- `avatar` - User avatar
|
||||
- `tooltip` - Hover tooltip
|
||||
- `accordion` - Collapsible sections
|
||||
- `collapsible` - Simple collapsible
|
||||
- `hover-card` - Rich hover card
|
||||
- `data-table` - Advanced data table with sorting/filtering
|
||||
|
||||
### Utility Components
|
||||
- `toggle` - Toggle button
|
||||
- `toggle-group` - Toggle button group
|
||||
- `sonner` - Toast notification library integration
|
||||
- `carousel` - Image/content carousel
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Adding a Single Component
|
||||
|
||||
```bash
|
||||
npx shadcn-ui add button
|
||||
```
|
||||
|
||||
This creates:
|
||||
- `components/ui/button.tsx`
|
||||
- Updates `components.json` if needed
|
||||
|
||||
### Adding Multiple Components
|
||||
|
||||
```bash
|
||||
npx shadcn-ui add button card input label
|
||||
```
|
||||
|
||||
### Using a Component
|
||||
|
||||
```tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||
|
||||
export default function Example() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Example Card</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button>Click me</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Component Composition Patterns
|
||||
|
||||
### Form with Validation
|
||||
|
||||
```tsx
|
||||
import { useForm } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import * as z from "zod"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { toast } from "sonner"
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8)
|
||||
})
|
||||
|
||||
export function LoginForm() {
|
||||
const form = useForm({
|
||||
resolver: zodResolver(schema)
|
||||
})
|
||||
|
||||
const onSubmit = (data) => {
|
||||
toast.success("Login successful!")
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input id="email" {...form.register("email")} />
|
||||
</div>
|
||||
<Button type="submit">Login</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Dialog with Form
|
||||
|
||||
```tsx
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
|
||||
export function CreateDialog() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Create New</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create Item</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form>
|
||||
<Input placeholder="Item name" />
|
||||
<Button type="submit">Create</Button>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Navigation with Dropdown
|
||||
|
||||
```tsx
|
||||
import { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuLink } from "@/components/ui/navigation-menu"
|
||||
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "@/components/ui/dropdown-menu"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export function AppNav() {
|
||||
return (
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink href="/">Home</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost">Products</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>Product A</DropdownMenuItem>
|
||||
<DropdownMenuItem>Product B</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### Modifying Component Styles
|
||||
|
||||
Components use Tailwind classes and can be customized:
|
||||
|
||||
```tsx
|
||||
// Customize colors via CSS variables in globals.css
|
||||
:root {
|
||||
--primary: 200 100% 50%; /* Change primary color */
|
||||
}
|
||||
|
||||
// Or override with props
|
||||
<Button className="bg-purple-600 hover:bg-purple-700">
|
||||
Custom Color
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Creating Variants
|
||||
|
||||
Use `class-variance-authority` (CVA) for variants:
|
||||
|
||||
```tsx
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
const alertVariants = cva(
|
||||
"rounded-lg border p-4",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-background text-foreground",
|
||||
destructive: "bg-destructive text-destructive-foreground",
|
||||
success: "bg-green-50 text-green-900"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default"
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Accessibility Features
|
||||
|
||||
All shadcn/ui components include:
|
||||
- [OK] Proper ARIA attributes
|
||||
- [OK] Keyboard navigation
|
||||
- [OK] Focus management
|
||||
- [OK] Screen reader support
|
||||
- [OK] Reduced motion support
|
||||
|
||||
### Example: Dialog Accessibility
|
||||
|
||||
```tsx
|
||||
<Dialog>
|
||||
{/* Auto-handled: */}
|
||||
{/* - Focus trap */}
|
||||
{/* - ESC to close */}
|
||||
{/* - aria-labelledby */}
|
||||
{/* - Backdrop click to close */}
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Title (used for aria-labelledby)</DialogTitle>
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Use Labels with Inputs
|
||||
|
||||
```tsx
|
||||
[OK] Good
|
||||
<div>
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<Input id="username" />
|
||||
</div>
|
||||
|
||||
[ERROR] Bad
|
||||
<Input placeholder="Username" /> {/* Placeholder is not a label */}
|
||||
```
|
||||
|
||||
### 2. Provide Accessible Names
|
||||
|
||||
```tsx
|
||||
[OK] Good
|
||||
<Button aria-label="Close menu">
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
[ERROR] Bad
|
||||
<Button>
|
||||
<X className="h-4 w-4" /> {/* No text or aria-label */}
|
||||
</Button>
|
||||
```
|
||||
|
||||
### 3. Handle Loading States
|
||||
|
||||
```tsx
|
||||
<Button disabled={isLoading}>
|
||||
{isLoading ? "Loading..." : "Submit"}
|
||||
</Button>
|
||||
```
|
||||
|
||||
### 4. Use Semantic HTML
|
||||
|
||||
```tsx
|
||||
[OK] Good
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
|
||||
[ERROR] Bad
|
||||
<div onClick={handleSubmit}>
|
||||
<Button type="button">Submit</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **Official Docs**: https://ui.shadcn.com
|
||||
- **Component Examples**: https://ui.shadcn.com/examples
|
||||
- **Radix UI Docs**: https://radix-ui.com
|
||||
- **GitHub**: https://github.com/shadcn-ui/ui
|
||||
|
||||
## Integration with This Skill
|
||||
|
||||
The skill installs these baseline components automatically:
|
||||
1. `button` - Primary actions
|
||||
2. `card` - Content containers
|
||||
3. `input` - Form inputs
|
||||
4. `label` - Form labels
|
||||
5. `dialog` - Modals
|
||||
6. `separator` - Visual dividers
|
||||
|
||||
Additional components can be added as needed:
|
||||
```bash
|
||||
npx shadcn-ui add [component-name]
|
||||
```
|
||||
@@ -0,0 +1,206 @@
|
||||
# Tailwind CSS v4 Migration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Tailwind CSS v4 introduces significant changes to configuration and usage patterns. This document outlines the differences and provides migration guidance.
|
||||
|
||||
## Current Status (as of January 2025)
|
||||
|
||||
- **Tailwind CSS v3.x**: Stable, widely adopted
|
||||
- **Tailwind CSS v4**: In development, alpha/beta releases available
|
||||
- **Recommendation**: Use v3 with forward-compatible patterns for production projects
|
||||
|
||||
## Key Differences: v3 vs v4
|
||||
|
||||
### Configuration Format
|
||||
|
||||
**v3 (Current)**
|
||||
```ts
|
||||
// tailwind.config.ts
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: ['./app/**/*.{ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: { /* ... */ }
|
||||
}
|
||||
},
|
||||
plugins: []
|
||||
} satisfies Config
|
||||
```
|
||||
|
||||
**v4 (Future)**
|
||||
```css
|
||||
/* @config directive in CSS */
|
||||
@config "./tailwind.config.js";
|
||||
|
||||
@theme {
|
||||
--color-brand: #3b82f6;
|
||||
--font-sans: system-ui, sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
### CSS Variables & Tokens
|
||||
|
||||
**v3 Pattern (Forward-Compatible)**
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// tailwind.config.ts
|
||||
colors: {
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))'
|
||||
}
|
||||
```
|
||||
|
||||
**v4 Expected Pattern**
|
||||
```css
|
||||
@theme {
|
||||
--color-background: oklch(100% 0 0);
|
||||
--color-foreground: oklch(20% 0.02 270);
|
||||
}
|
||||
```
|
||||
|
||||
### Plugin System
|
||||
|
||||
**v3**: JavaScript-based plugins in config file
|
||||
**v4**: CSS-based plugins with `@plugin` directive
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Phase 1: v3 with Forward-Compatible Patterns (Current)
|
||||
|
||||
Use CSS variables extensively:
|
||||
- Define semantic tokens (--primary, --background, etc.)
|
||||
- Use HSL/OKLCH color space for easier manipulation
|
||||
- Structure CSS variables to match expected v4 patterns
|
||||
|
||||
### Phase 2: v4 Alpha/Beta Testing (When Available)
|
||||
|
||||
Test v4 alphas in non-production projects:
|
||||
```bash
|
||||
npm install tailwindcss@next
|
||||
```
|
||||
|
||||
Update configuration:
|
||||
- Move color tokens to `@theme` directive
|
||||
- Convert plugins to CSS-based format
|
||||
- Test build performance and output
|
||||
|
||||
### Phase 3: v4 Stable Migration (Future)
|
||||
|
||||
When v4 is stable:
|
||||
1. Update package: `npm install tailwindcss@latest`
|
||||
2. Migrate config format (may have migration tool)
|
||||
3. Test thoroughly across all components
|
||||
4. Update documentation
|
||||
|
||||
## Recommended Patterns for This Skill
|
||||
|
||||
### Use CSS Custom Properties
|
||||
|
||||
[OK] **Good (Forward-Compatible)**
|
||||
```css
|
||||
:root {
|
||||
--radius: 0.5rem;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: var(--radius);
|
||||
background: hsl(var(--primary));
|
||||
}
|
||||
```
|
||||
|
||||
[ERROR] **Avoid (Hard-Coded)**
|
||||
```css
|
||||
.card {
|
||||
border-radius: 0.5rem;
|
||||
background: #1e293b;
|
||||
}
|
||||
```
|
||||
|
||||
### Semantic Color Naming
|
||||
|
||||
Use semantic names that describe purpose, not appearance:
|
||||
- [OK] `--primary`, `--destructive`, `--muted`
|
||||
- [ERROR] `--blue-500`, `--red-600`, `--gray-200`
|
||||
|
||||
### HSL Color Space
|
||||
|
||||
Use HSL (or OKLCH when v4 is available) for better color manipulation:
|
||||
```css
|
||||
/* HSL: Hue Saturation Lightness */
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Usage in CSS */
|
||||
background: hsl(var(--primary));
|
||||
background: hsl(var(--primary) / 0.8); /* With alpha */
|
||||
```
|
||||
|
||||
## Checking for v4 Availability
|
||||
|
||||
```bash
|
||||
# Check installed version
|
||||
npm list tailwindcss
|
||||
|
||||
# Check latest available version
|
||||
npm view tailwindcss versions --json
|
||||
|
||||
# Install specific version
|
||||
npm install tailwindcss@4.0.0 # When available
|
||||
```
|
||||
|
||||
## V4-Specific Features to Watch For
|
||||
|
||||
### CSS-First Configuration
|
||||
- `@theme` directive for token definition
|
||||
- `@plugin` for extending functionality
|
||||
- Native CSS nesting support
|
||||
|
||||
### Improved Color System
|
||||
- OKLCH color space support
|
||||
- Better color mixing
|
||||
- Improved dark mode handling
|
||||
|
||||
### Performance Improvements
|
||||
- Faster build times
|
||||
- Smaller CSS output
|
||||
- Better JIT performance
|
||||
|
||||
### New Utilities
|
||||
- Container queries (better support)
|
||||
- Cascade layers
|
||||
- View transitions
|
||||
|
||||
## Resources
|
||||
|
||||
- **Tailwind CSS Docs**: https://tailwindcss.com/docs
|
||||
- **V4 Alpha Docs**: https://tailwindcss.com/docs/v4-alpha
|
||||
- **GitHub Discussions**: https://github.com/tailwindlabs/tailwindcss/discussions
|
||||
- **Upgrade Guide**: https://tailwindcss.com/docs/upgrade-guide (when v4 is stable)
|
||||
|
||||
## Notes for Skill Usage
|
||||
|
||||
When this skill runs:
|
||||
1. Check Tailwind version: `npm view tailwindcss version`
|
||||
2. If v4 stable is available: Use v4 configuration patterns
|
||||
3. If v4 is not available: Use v3 with forward-compatible CSS variables
|
||||
4. Add comments in generated files indicating v4 migration points:
|
||||
```ts
|
||||
// TODO: When upgrading to Tailwind v4, move these tokens to @theme directive
|
||||
```
|
||||
|
||||
This approach ensures the skill produces production-ready code today while being ready for v4 when it arrives.
|
||||
548
skills/tailwind-shadcn-ui-setup/references/theme-tokens.md
Normal file
548
skills/tailwind-shadcn-ui-setup/references/theme-tokens.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# Theme Token System
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the CSS custom property-based design token system used for Tailwind + shadcn/ui theming.
|
||||
|
||||
## Token Philosophy
|
||||
|
||||
### Semantic Naming
|
||||
Tokens describe **purpose**, not appearance:
|
||||
- [OK] `--primary` (describes role)
|
||||
- [ERROR] `--blue-600` (describes appearance)
|
||||
|
||||
### Benefits
|
||||
- Easy theme switching
|
||||
- Consistent design system
|
||||
- Automatic dark mode support
|
||||
- Forward-compatible with Tailwind v4
|
||||
|
||||
## Color Token Structure
|
||||
|
||||
### HSL Format
|
||||
|
||||
All colors use HSL (Hue, Saturation, Lightness) format without the `hsl()` wrapper:
|
||||
|
||||
```css
|
||||
/* Define as space-separated values */
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Use with hsl() wrapper */
|
||||
background-color: hsl(var(--primary));
|
||||
|
||||
/* With alpha transparency */
|
||||
background-color: hsl(var(--primary) / 0.5);
|
||||
```
|
||||
|
||||
### Why HSL?
|
||||
- **Intuitive**: Easier to adjust than RGB
|
||||
- **Lightness control**: Simple to create shades
|
||||
- **Alpha transparency**: Works with modern CSS syntax
|
||||
- **Tooling**: Better design tool support
|
||||
|
||||
## Core Color Tokens
|
||||
|
||||
### Base Colors
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Background & Foreground */
|
||||
--background: 0 0% 100%; /* Pure white */
|
||||
--foreground: 222.2 84% 4.9%; /* Near black text */
|
||||
|
||||
/* Card (elevated surfaces) */
|
||||
--card: 0 0% 100%; /* White card background */
|
||||
--card-foreground: 222.2 84% 4.9%; /* Card text */
|
||||
|
||||
/* Popover (floating UI) */
|
||||
--popover: 0 0% 100%; /* White popover */
|
||||
--popover-foreground: 222.2 84% 4.9%; /* Popover text */
|
||||
}
|
||||
```
|
||||
|
||||
### Semantic Colors
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Primary (brand color, main actions) */
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
/* Secondary (less prominent actions) */
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Muted (disabled states, subtle backgrounds) */
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
/* Accent (highlights, hover states) */
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Destructive (errors, delete actions) */
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
}
|
||||
```
|
||||
|
||||
### UI Element Colors
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Borders */
|
||||
--border: 214.3 31.8% 91.4%; /* Subtle border */
|
||||
--input: 214.3 31.8% 91.4%; /* Input border */
|
||||
|
||||
/* Focus & Selection */
|
||||
--ring: 222.2 84% 4.9%; /* Focus ring */
|
||||
|
||||
/* Status Colors (optional) */
|
||||
--success: 142 76% 36%;
|
||||
--success-foreground: 0 0% 100%;
|
||||
--warning: 38 92% 50%;
|
||||
--warning-foreground: 0 0% 100%;
|
||||
--info: 199 89% 48%;
|
||||
--info-foreground: 0 0% 100%;
|
||||
}
|
||||
```
|
||||
|
||||
## Dark Mode Tokens
|
||||
|
||||
### Dark Mode Strategy
|
||||
|
||||
Use `.dark` class to override tokens:
|
||||
|
||||
```css
|
||||
.dark {
|
||||
/* Base */
|
||||
--background: 222.2 84% 4.9%; /* Near black */
|
||||
--foreground: 210 40% 98%; /* Near white */
|
||||
|
||||
/* Card */
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
/* Primary */
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
/* Secondary */
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
/* Muted */
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
/* Accent */
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
/* Destructive */
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
/* Borders */
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
|
||||
/* Focus */
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
```
|
||||
|
||||
## Theme Presets
|
||||
|
||||
### Zinc (Default)
|
||||
|
||||
Cool, neutral gray tones. Professional and versatile.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--border: 240 5.9% 90%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
}
|
||||
```
|
||||
|
||||
### Slate
|
||||
|
||||
Slightly cooler than Zinc. Tech-focused feel.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
}
|
||||
```
|
||||
|
||||
### Neutral
|
||||
|
||||
True neutral grays. Clean and minimal.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--border: 0 0% 89.8%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--border: 0 0% 14.9%;
|
||||
}
|
||||
```
|
||||
|
||||
## Spacing & Sizing Tokens
|
||||
|
||||
### Border Radius
|
||||
|
||||
```css
|
||||
:root {
|
||||
--radius: 0.5rem; /* Base radius: 8px */
|
||||
}
|
||||
|
||||
/* Usage in Tailwind config */
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
}
|
||||
```
|
||||
|
||||
### Font Family (Optional)
|
||||
|
||||
```css
|
||||
:root {
|
||||
--font-sans: ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: ui-monospace, 'Cascadia Code', monospace;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage in Tailwind Config
|
||||
|
||||
### Extending Theme
|
||||
|
||||
```ts
|
||||
// tailwind.config.ts
|
||||
export default {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))'
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))'
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))'
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))'
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))'
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))'
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))'
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using Tokens in Components
|
||||
|
||||
### In CSS
|
||||
|
||||
```css
|
||||
.custom-component {
|
||||
background-color: hsl(var(--background));
|
||||
color: hsl(var(--foreground));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
/* With alpha */
|
||||
.overlay {
|
||||
background-color: hsl(var(--background) / 0.8);
|
||||
}
|
||||
```
|
||||
|
||||
### With Tailwind Utilities
|
||||
|
||||
```tsx
|
||||
<div className="bg-background text-foreground border-border">
|
||||
<h1 className="text-primary">Heading</h1>
|
||||
<p className="text-muted-foreground">Subtitle</p>
|
||||
<Button variant="destructive">Delete</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Customization Guide
|
||||
|
||||
### Changing Primary Color
|
||||
|
||||
1. **Find your HSL values**: Use a color picker that shows HSL
|
||||
2. **Update light mode**:
|
||||
```css
|
||||
:root {
|
||||
--primary: 270 80% 45%; /* Purple example */
|
||||
--primary-foreground: 0 0% 100%; /* White text */
|
||||
}
|
||||
```
|
||||
3. **Update dark mode**:
|
||||
```css
|
||||
.dark {
|
||||
--primary: 270 80% 65%; /* Lighter purple */
|
||||
--primary-foreground: 240 10% 10%; /* Dark text */
|
||||
}
|
||||
```
|
||||
4. **Test contrast**: Use WebAIM contrast checker
|
||||
|
||||
### Creating Custom Tokens
|
||||
|
||||
```css
|
||||
/* Add to globals.css */
|
||||
:root {
|
||||
/* Custom tokens */
|
||||
--sidebar-width: 16rem;
|
||||
--header-height: 4rem;
|
||||
--brand-gradient: linear-gradient(135deg, hsl(var(--primary)), hsl(var(--accent)));
|
||||
}
|
||||
|
||||
/* Use in components */
|
||||
.sidebar {
|
||||
width: var(--sidebar-width);
|
||||
}
|
||||
```
|
||||
|
||||
### Adding More Semantic Colors
|
||||
|
||||
```css
|
||||
:root {
|
||||
--success: 142 76% 36%;
|
||||
--success-foreground: 0 0% 100%;
|
||||
--warning: 38 92% 50%;
|
||||
--warning-foreground: 0 0% 100%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--success: 142 71% 45%;
|
||||
--warning: 38 92% 60%;
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// Add to tailwind.config.ts
|
||||
colors: {
|
||||
success: {
|
||||
DEFAULT: 'hsl(var(--success))',
|
||||
foreground: 'hsl(var(--success-foreground))'
|
||||
},
|
||||
warning: {
|
||||
DEFAULT: 'hsl(var(--warning))',
|
||||
foreground: 'hsl(var(--warning-foreground))'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Pair Background/Foreground
|
||||
|
||||
```tsx
|
||||
[OK] Good
|
||||
<div className="bg-primary text-primary-foreground">
|
||||
Readable text
|
||||
</div>
|
||||
|
||||
[ERROR] Bad
|
||||
<div className="bg-primary text-foreground">
|
||||
Low contrast
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. Test Both Themes
|
||||
|
||||
Always verify colors in both light and dark mode:
|
||||
```bash
|
||||
# Use browser DevTools to toggle:
|
||||
document.documentElement.classList.toggle('dark')
|
||||
```
|
||||
|
||||
### 3. Use Semantic Tokens
|
||||
|
||||
```tsx
|
||||
[OK] Good (semantic)
|
||||
<p className="text-muted-foreground">Helper text</p>
|
||||
<Button variant="destructive">Delete</Button>
|
||||
|
||||
[ERROR] Bad (hard-coded)
|
||||
<p className="text-gray-500 dark:text-gray-400">Helper text</p>
|
||||
<Button className="bg-red-600">Delete</Button>
|
||||
```
|
||||
|
||||
### 4. Maintain Contrast Ratios
|
||||
|
||||
- Normal text: ≥ 4.5:1
|
||||
- Large text: ≥ 3:1
|
||||
- UI components: ≥ 3:1
|
||||
|
||||
## Tools for Theme Development
|
||||
|
||||
### Color Pickers with HSL
|
||||
- [HSL Color Picker](https://hslpicker.com/)
|
||||
- [Coolors](https://coolors.co/)
|
||||
- [Palettte App](https://palettte.app/)
|
||||
|
||||
### Contrast Checkers
|
||||
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
|
||||
- [Contrast Ratio](https://contrast-ratio.com/)
|
||||
- Chrome DevTools (Lighthouse)
|
||||
|
||||
### Theme Generators
|
||||
- [Realtime Colors](https://realtimecolors.com/)
|
||||
- [shadcn/ui Themes](https://ui.shadcn.com/themes)
|
||||
- [Tailwind Color Generator](https://uicolors.app/create)
|
||||
|
||||
## Example: Complete Theme
|
||||
|
||||
```css
|
||||
/* globals.css */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
/* Base */
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
|
||||
/* UI Elements */
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
|
||||
/* Primary Brand */
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
/* Secondary Actions */
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
|
||||
/* Muted Elements */
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
|
||||
/* Accents */
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
|
||||
/* Destructive */
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
/* Borders & Inputs */
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5.9% 10%;
|
||||
|
||||
/* Radius */
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **HSL Color Space**: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl
|
||||
- **shadcn/ui Theming**: https://ui.shadcn.com/docs/theming
|
||||
- **Tailwind CSS Customization**: https://tailwindcss.com/docs/customizing-colors
|
||||
- **Design Tokens**: https://www.w3.org/community/design-tokens/
|
||||
Reference in New Issue
Block a user