--- name: design description: Auto-activates when user mentions UI design, design systems, or component design. Expert in design principles, accessibility, and component architecture. category: design --- # UI/UX Design Guidelines ## Core Principles 1. **User-Centered Design** - Always design for the user's needs, not your preferences 2. **Consistency** - Maintain consistent patterns across the entire interface 3. **Accessibility First** - Design for all users, including those with disabilities 4. **Progressive Disclosure** - Show only what's needed, when it's needed 5. **Feedback & Affordance** - Make interactions clear and provide immediate feedback ## Design System Foundations ### Design Tokens Use design tokens for all visual properties to ensure consistency and enable theming: ```css /* ✅ Good: Use design tokens */ :root { /* Colors */ --color-primary: #0066ff; --color-secondary: #6b7280; --color-success: #10b981; --color-error: #ef4444; --color-warning: #f59e0b; /* Spacing (8px grid) */ --space-xs: 0.25rem; /* 4px */ --space-sm: 0.5rem; /* 8px */ --space-md: 1rem; /* 16px */ --space-lg: 1.5rem; /* 24px */ --space-xl: 2rem; /* 32px */ --space-2xl: 3rem; /* 48px */ /* Typography */ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; --font-mono: "SF Mono", Monaco, "Cascadia Code", monospace; --text-xs: 0.75rem; /* 12px */ --text-sm: 0.875rem; /* 14px */ --text-base: 1rem; /* 16px */ --text-lg: 1.125rem; /* 18px */ --text-xl: 1.25rem; /* 20px */ --text-2xl: 1.5rem; /* 24px */ /* Border radius */ --radius-sm: 0.25rem; --radius-md: 0.5rem; --radius-lg: 1rem; --radius-full: 9999px; /* Shadows */ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); } ``` ### ❌ Bad: Hardcoded values ```css .button { padding: 12px 20px; /* Should use design tokens */ background: #0066ff; /* Should use --color-primary */ font-size: 14px; /* Should use --text-sm */ } ``` ## Component Design Patterns ### Buttons ```tsx // ✅ Good: Comprehensive button with states interface ButtonProps { variant: 'primary' | 'secondary' | 'ghost' | 'danger'; size?: 'sm' | 'md' | 'lg'; disabled?: boolean; loading?: boolean; leftIcon?: React.ReactNode; rightIcon?: React.ReactNode; children: React.ReactNode; onClick?: () => void; } export function Button({ variant = 'primary', size = 'md', disabled = false, loading = false, leftIcon, rightIcon, children, onClick, ...props }: ButtonProps) { return ( ); } ``` ### Form Inputs ```tsx // ✅ Good: Accessible form input with error states interface InputProps { label: string; id: string; type?: string; error?: string; hint?: string; required?: boolean; disabled?: boolean; } export function Input({ label, id, type = 'text', error, hint, required = false, disabled = false, ...props }: InputProps) { return (
{hint && !error && (

{hint}

)} {error && ( )}
); } ``` ## Accessibility (a11y) Requirements ### Semantic HTML ```tsx // ✅ Good: Semantic HTML

Article Title

Article content...

// ❌ Bad: Non-semantic divs
Home
``` ### ARIA Labels & Roles ```tsx // ✅ Good: Proper ARIA usage
{message}
// Screen reader only text Loading... ``` ### Keyboard Navigation ```tsx // ✅ Good: Full keyboard support function Dropdown() { const handleKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case 'Escape': close(); break; case 'ArrowDown': focusNextItem(); break; case 'ArrowUp': focusPreviousItem(); break; case 'Enter': case ' ': selectItem(); break; } }; return (
{/* Items */}
); } ``` ### Color Contrast (WCAG AA) - Normal text: 4.5:1 minimum - Large text (18pt+): 3:1 minimum - UI components & graphics: 3:1 minimum ```tsx // ✅ Good: Sufficient contrast

Primary text with high contrast

// ❌ Bad: Insufficient contrast

Low contrast text (hard to read)

``` ## Layout & Spacing ### 8px Grid System Always use multiples of 8 for spacing and dimensions: ```tsx // ✅ Good: 8px grid
{/* 16px, 8px */}
{/* 48px, 128px */}
// ❌ Bad: Random values
``` ### Responsive Breakpoints (Mobile-First) ```tsx // ✅ Good: Mobile-first responsive
``` ### Whitespace & Hierarchy ```tsx // ✅ Good: Clear visual hierarchy

Section Title

Section description

{/* Related content grouped together */}
``` ## Typography ### Type Scale ```tsx // ✅ Good: Consistent type scale

Main Heading

Section Heading

Subsection

Body text

Helper text ``` ### Line Height & Letter Spacing ```css /* ✅ Good: Readable typography */ .text-display { font-size: 3rem; line-height: 1.2; /* Tight for headings */ letter-spacing: -0.02em; } .text-body { font-size: 1rem; line-height: 1.6; /* Relaxed for body text */ letter-spacing: 0; } ``` ## Color System ### Semantic Colors ```tsx // ✅ Good: Semantic color usage
``` ### Dark Mode ```tsx // ✅ Good: Always include dark mode

Title

Description

``` ## User Feedback & States ### Loading States ```tsx // ✅ Good: Clear loading indication {isLoading ? (
) : ( )} ``` ### Empty States ```tsx // ✅ Good: Helpful empty state function EmptyState() { return (
); } ``` ### Error States ```tsx // ✅ Good: Actionable error message {error && (
)} ``` ## Micro-interactions ### Hover & Focus States ```tsx // ✅ Good: Clear interactive states ``` ### Animations (Subtle & Purposeful) ```tsx // ✅ Good: Smooth entrance animation
Content appears smoothly
// Respect prefers-reduced-motion
``` ## Mobile-First Considerations ### Touch Targets ```tsx // ✅ Good: Minimum 44x44px touch targets // Increase tap area without visual size ``` ### Safe Areas (iOS/Android) ```css /* ✅ Good: Respect device safe areas */ .container { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); } ``` ## Design-to-Code Workflow When converting designs to code: 1. **Analyze the Design** - Identify reusable components - Note color palette and typography - Map out spacing system 2. **Build from Atoms to Organisms** - Start with design tokens - Create base components (buttons, inputs) - Compose into larger patterns - Build full layouts 3. **Ensure Responsiveness** - Mobile-first approach - Test at all breakpoints - Progressive enhancement 4. **Add Interactivity** - Hover/focus states - Loading/error states - Smooth transitions - Keyboard navigation 5. **Accessibility Audit** - Semantic HTML - ARIA labels - Color contrast - Keyboard navigation - Screen reader testing ## Common Patterns ### Card Component ```tsx // ✅ Good: Flexible card component Card Title Card description goes here Main content ``` ### Modal/Dialog ```tsx // ✅ Good: Accessible modal Dialog Title

Dialog content goes here

``` ## Advanced ARIA Patterns ### ✅ Good: Accessible Dialog/Modal ```tsx import * as React from 'react'; import * as Dialog from '@radix-ui/react-dialog'; function AccessibleModal() { return ( Delete Account This action cannot be undone. This will permanently delete your account.
); } ``` ### ✅ Good: Accessible Dropdown Menu ```tsx import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; function AccessibleDropdown() { return ( ); } ``` ### ✅ Good: Accessible Tabs Pattern ```tsx import * as Tabs from '@radix-ui/react-tabs'; function AccessibleTabs() { return ( Profile Settings Billing

Profile

Manage your profile settings here.

Settings

Configure your application settings.

Billing

Manage your billing information.

); } ``` ### ✅ Good: Accessible Accordion ```tsx import * as Accordion from '@radix-ui/react-accordion'; function AccessibleAccordion() { return ( What is accessibility? Accessibility ensures that people with disabilities can perceive, understand, navigate, and interact with websites and tools. Why is WCAG important? WCAG (Web Content Accessibility Guidelines) provides a standard for making web content accessible to all users, including those with disabilities. ); } ``` ### ✅ Good: Toast Notifications with Live Regions ```tsx import * as Toast from '@radix-ui/react-toast'; function ToastNotifications() { const [open, setOpen] = React.useState(false); return ( ); } ``` ## Complete Keyboard Navigation Guide ### ✅ Good: Focus Management in Modals ```tsx import { useEffect, useRef } from 'react'; function FocusTrappedModal({ isOpen, onClose, children }) { const modalRef = useRef(null); const previousFocusRef = useRef(null); useEffect(() => { if (isOpen) { // Store currently focused element previousFocusRef.current = document.activeElement as HTMLElement; // Focus first focusable element in modal const firstFocusable = modalRef.current?.querySelector( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ) as HTMLElement; firstFocusable?.focus(); // Trap focus within modal const handleKeyDown = (e: KeyboardEvent) => { if (e.key !== 'Tab') return; const focusableElements = Array.from( modalRef.current?.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ) || [] ) as HTMLElement[]; const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1]; if (e.shiftKey && document.activeElement === firstElement) { e.preventDefault(); lastElement.focus(); } else if (!e.shiftKey && document.activeElement === lastElement) { e.preventDefault(); firstElement.focus(); } }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); // Restore focus when modal closes previousFocusRef.current?.focus(); }; } }, [isOpen]); if (!isOpen) return null; return (
{children}
); } ``` ### ✅ Good: Skip Links ```tsx // At the top of your app function App() { return ( <> Skip to main content
{/* Main content */}
); } ``` ### ✅ Good: Keyboard Shortcuts ```tsx import { useEffect } from 'react'; function useKeyboardShortcut( key: string, callback: () => void, modifiers: { ctrl?: boolean; shift?: boolean; alt?: boolean } = {} ) { useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { const matchesModifiers = (modifiers.ctrl ? e.ctrlKey || e.metaKey : !e.ctrlKey && !e.metaKey) && (modifiers.shift ? e.shiftKey : !e.shiftKey) && (modifiers.alt ? e.altKey : !e.altKey); if (e.key === key && matchesModifiers) { e.preventDefault(); callback(); } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [key, callback, modifiers]); } // Usage function Editor() { useKeyboardShortcut('s', handleSave, { ctrl: true }); useKeyboardShortcut('k', openSearch, { ctrl: true }); useKeyboardShortcut('/', focusSearch); return
...
; } ``` ## Responsive Design Complete Guide ### ✅ Good: Mobile-First Breakpoint Strategy ```tsx // tailwind.config.js module.exports = { theme: { screens: { 'sm': '640px', // Small devices (phones, 640px and up) 'md': '768px', // Medium devices (tablets, 768px and up) 'lg': '1024px', // Large devices (desktops, 1024px and up) 'xl': '1280px', // Extra large devices (large desktops, 1280px and up) '2xl': '1536px', // 2X large devices (larger desktops, 1536px and up) } } }; // Mobile-first component function ResponsiveGrid() { return (
{items.map(item => )}
); } ``` ### ✅ Good: Container Queries (Modern Approach) ```tsx // Use container queries for truly responsive components function ProductCard() { return (

Product Name

Description

); } ``` ### ✅ Good: Responsive Typography ```tsx // Fluid typography using clamp() const styles = { heading: { fontSize: 'clamp(1.5rem, 5vw, 3rem)', // Min 24px, scales with viewport, max 48px lineHeight: '1.2', }, body: { fontSize: 'clamp(1rem, 2.5vw, 1.125rem)', // Min 16px, max 18px lineHeight: '1.6', } }; // Or with Tailwind

Responsive Heading

``` ### ✅ Good: Touch-Friendly Interfaces ```tsx // Minimum touch target: 44x44px (iOS), 48x48px (Android) function TouchFriendlyButton() { return ( ); } // Increase tap area without changing visual size function IconButton() { return ( ); } ``` ### ✅ Good: iOS/Android Safe Areas ```css /* Respect device safe areas (notches, rounded corners) */ .app-container { padding-top: env(safe-area-inset-top); padding-right: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); } /* Fixed header accounting for safe areas */ .fixed-header { position: fixed; top: 0; left: 0; right: 0; padding-top: calc(1rem + env(safe-area-inset-top)); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); } ``` ## Design Tokens & Design Systems ### ✅ Good: Comprehensive Design Token System ```typescript // design-tokens.ts export const tokens = { colors: { // Brand colors primary: { 50: '#eff6ff', 100: '#dbeafe', 500: '#3b82f6', 600: '#2563eb', 900: '#1e3a8a', }, // Semantic colors success: '#10b981', warning: '#f59e0b', error: '#ef4444', info: '#3b82f6', // Neutrals gray: { 50: '#f9fafb', 100: '#f3f4f6', 500: '#6b7280', 900: '#111827', } }, spacing: { 0: '0', 1: '0.25rem', // 4px 2: '0.5rem', // 8px 3: '0.75rem', // 12px 4: '1rem', // 16px 6: '1.5rem', // 24px 8: '2rem', // 32px 12: '3rem', // 48px 16: '4rem', // 64px }, typography: { fontFamily: { sans: ['Inter', 'system-ui', 'sans-serif'], mono: ['JetBrains Mono', 'monospace'], }, fontSize: { xs: ['0.75rem', { lineHeight: '1rem' }], // 12px sm: ['0.875rem', { lineHeight: '1.25rem' }], // 14px base: ['1rem', { lineHeight: '1.5rem' }], // 16px lg: ['1.125rem', { lineHeight: '1.75rem' }], // 18px xl: ['1.25rem', { lineHeight: '1.75rem' }], // 20px '2xl': ['1.5rem', { lineHeight: '2rem' }], // 24px }, fontWeight: { normal: '400', medium: '500', semibold: '600', bold: '700', } }, borderRadius: { none: '0', sm: '0.125rem', // 2px md: '0.375rem', // 6px lg: '0.5rem', // 8px xl: '0.75rem', // 12px full: '9999px', }, shadows: { sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)', md: '0 4px 6px -1px rgb(0 0 0 / 0.1)', lg: '0 10px 15px -3px rgb(0 0 0 / 0.1)', xl: '0 20px 25px -5px rgb(0 0 0 / 0.1)', }, animation: { duration: { fast: '150ms', base: '200ms', slow: '300ms', }, easing: { linear: 'linear', in: 'cubic-bezier(0.4, 0, 1, 1)', out: 'cubic-bezier(0, 0, 0.2, 1)', inOut: 'cubic-bezier(0.4, 0, 0.2, 1)', } }, zIndex: { dropdown: 1000, sticky: 1020, modal: 1040, popover: 1050, toast: 1060, } } as const; ``` ### ✅ Good: Component Variant System ```tsx // Using class-variance-authority (CVA) import { cva, type VariantProps } from 'class-variance-authority'; const button = cva( // Base styles 'inline-flex items-center justify-center rounded-md font-medium transition-colors', { variants: { variant: { primary: 'bg-blue-600 text-white hover:bg-blue-700', secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300', ghost: 'hover:bg-gray-100', danger: 'bg-red-600 text-white hover:bg-red-700', }, size: { sm: 'h-8 px-3 text-sm', md: 'h-10 px-4 text-base', lg: 'h-12 px-6 text-lg', }, disabled: { true: 'opacity-50 cursor-not-allowed pointer-events-none', } }, defaultVariants: { variant: 'primary', size: 'md', } } ); type ButtonProps = VariantProps & React.ButtonHTMLAttributes; export function Button({ variant, size, disabled, className, ...props }: ButtonProps) { return (