
Software Ui Ux Design
- 623 installs
- 73 repo stars
- Updated July 13, 2026
- vasilyu1983/ai-agents-public
software-ui-ux-design is a design skill that generates consistent, high-quality UI/UX designs and implementation guidance for web applications and interfaces.
About
software-ui-ux-design is a UI/UX design skill from vasilyu1983/ai-agents-public listed on skills.sh with 505 installs. It helps developers produce consistent, high-quality interface designs and practical implementation guidance for web applications. Developers reach for software-ui-ux-design when they need structured UX decisions and visual direction before or during frontend implementation, rather than improvising layouts component by component. The skill focuses on web application interfaces where design consistency and developer-ready guidance matter.
- Generates complete UI/UX specifications from natural language prompts
- Produces component hierarchies, color systems, typography scales and interaction patterns
- Creates implementation-ready designs optimized for Claude, Cursor and modern frontend stacks
- Enforces design system consistency across multiple screens and states
Software Ui Ux Design by the numbers
- 623 all-time installs (skills.sh)
- +16 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #524 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vasilyu1983/ai-agents-public --skill software-ui-ux-designAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 623 |
|---|---|
| repo stars | ★ 73 |
| Last updated | July 13, 2026 |
| Repository | vasilyu1983/ai-agents-public ↗ |
How do you design consistent web app UI/UX?
Generate consistent, high-quality UI/UX designs and implementation guidance for web applications and interfaces.
Who is it for?
Developers building web applications who need consistent UI/UX design direction and implementation-ready guidance without a dedicated design team.
Skip if: Native mobile-only UI work, pure backend services with no interface, or teams with finalized Figma files requiring only pixel-perfect CSS translation.
When should I use this skill?
The user asks for web UI/UX design, interface consistency, or implementation guidance for application screens and flows.
What you get
UI/UX design specifications and implementation guidance documents for web application interfaces
- UI/UX design specs
- implementation guidance
By the numbers
- 505 installs on skills.sh
- Source repository: vasilyu1983/ai-agents-public
Files
Software UI/UX Design
Design intuitive, accessible, user-centered interfaces.
Baselines (Mar 2026):
- Accessibility: WCAG 2.2 Level AA — W3C
- Performance: Core Web Vitals (LCP ≤2.5s, INP ≤200ms, CLS ≤0.1) — web.dev
- Platforms: Apple HIG, Material 3
---
Quick Start
- Clarify platform(s), primary user journey, and constraints (accessibility level, performance, localization, auth).
- Choose track: audit an existing UI (heuristics + state matrix + WCAG) or design a new UI (IA + flows + UI spec).
- Produce artifacts: recommendations, acceptance criteria, and a handoff spec (components, states, copy, tokens).
---
Decision Tree
Design challenge:
├─ What to build? → Use software-ux-research first
├─ Improving existing UI?
│ ├─ Usability issues → Heuristic review
│ ├─ Accessibility gaps → WCAG 2.2 audit
│ ├─ Inconsistency → Design system alignment
│ └─ Conversion issues → CRO audit
├─ Building new UI?
│ └─ references/ui-generation-workflows.md
├─ Non-technical users / simplification?
│ └─ references/simplification-patterns.md
├─ Specific demographics?
│ └─ references/demographic-inclusive-design.md
└─ Platform constraints?
├─ Web → semantics + focus + reflow
├─ iOS → system nav + Dynamic Type
└─ Android → Material + edge-to-edge---
Interaction Checklist
| Goal | Do | Avoid |
|---|---|---|
| Clarity | One primary action per view | Competing CTAs |
| Affordances | Native controls, strong signifiers | Clickable divs, hover-only |
| Feedback | Immediate visual response | Silent taps |
| Error prevention | Constrain inputs, show examples | Submit-then-fail |
| Error recovery | Specific message + next step | "Something went wrong" |
| Consistency | Reuse patterns and terms | Same term, different meanings |
---
State Matrix
| State | Treatment | When |
|---|---|---|
| Loading | Placeholder matching layout | Data fetching |
| Empty | Message + CTA | Zero items |
| Error | Alert + retry action | Request fails |
| Offline | Banner + cached indicator | No network |
| Degraded | Warning + limited functionality | Partial failure |
---
Platform Constraints
Web
- Semantic HTML first (no "div soup")
- ARIA only when needed
- Manage focus on SPA navigation
- Reflow at 320 CSS px (WCAG 1.4.10)
- Target size ≥24px (WCAG 2.5.8)
iOS
- System navigation (tab bar, nav bar)
- Dynamic Type support
- Dark mode + system materials
- Handle Safe Areas
Android
- Material 3 components
- Dynamic Color (Material You)
- Edge-to-edge content
- Handle predictive back
---
WCAG 2.2 Key Changes
| Requirement | Implementation |
|---|---|
| Focus not obscured | Keep focus visible with sticky UI |
| Focus appearance | Clear visible indicator |
| Dragging movements | Non-drag alternatives |
| Target size | ≥24×24 CSS px |
| Redundant entry | Don't re-request known info |
| Accessible auth | Avoid cognitive tests |
Reduced Motion
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}---
Design Tokens
{
"color": {
"primary": {
"$value": "#0066cc",
"$type": "color"
}
},
"spacing": {
"sm": {
"$value": "8px",
"$type": "dimension"
}
}
}| Layer | Examples | Purpose |
|---|---|---|
| Primitive | blue-500, 16px | Raw values |
| Semantic | color-primary | Intent-based |
| Component | button-bg | Component-specific |
---
Resources
| Resource | Purpose |
|---|---|
| references/implementation-research-workflow.md | Research before building |
| references/design-systems.md | Design system patterns |
| references/component-library-comparison.md | shadcn, MUI, Radix |
| references/nielsen-heuristics.md | Heuristic evaluation |
| references/wcag-accessibility.md | WCAG compliance |
| references/demographic-inclusive-design.md | Age-specific UX |
| references/neurodiversity-design.md | ADHD, autism, dyslexia |
| references/ui-generation-workflows.md | UI from scratch |
| references/ai-design-tools-2025.md | Figma AI, v0 |
| references/cro-framework.md | Conversion optimization |
| references/mobile-ux-patterns.md | Mobile UX: thumb zone, navigation, gestures, platform patterns |
| references/form-design-patterns.md | Form UX: layout, validation, multi-step, accessibility |
| references/dark-mode-theming.md | Dark mode & multi-theme: tokens, CSS, platform implementation |
| references/ai-automation-ux.md | AI/automation UX: chatbots, agents, progressive disclosure |
| references/cultural-design-patterns.md | Cross-cultural design: RTL, CJK, color semiotics, locale UX |
| references/frontend-aesthetics-2025.md | Visual design trends 2025: glassmorphism, variable fonts, 3D |
| references/simplification-patterns.md | Interface simplification for non-technical users, digital literacy spectrum |
| references/modern-ux-patterns-2025.md | Modern UX patterns: command palettes, skeleton states, dark mode, 2026 trends |
| references/data-visualization-ux.md | Data viz: chart selection, dashboards, accessible charts |
| references/typography-systems.md | Type scales, font pairing, variable fonts, design tokens |
| references/performance-ux-vitals.md | Core Web Vitals UX, perceived performance, loading patterns |
| references/prototype-to-production.md | Prototype-to-production alignment, dashboard QA, design-to-ship checks |
| references/operational-playbook.md | Decision frameworks |
Templates
| Template | Purpose |
|---|---|
| assets/design-brief.md | Design brief |
| assets/ux-review-checklist.md | UX review |
| assets/ui-generation/full-ui-spec.md | UI spec |
| assets/audits/cro-audit-template.md | CRO audit |
| assets/accessibility/template-wcag-testing.md | WCAG testing |
| assets/audits/simplification-audit-template.md | Simplification audit |
| assets/design-systems/template-design-system.md | Design system setup |
| assets/component-libraries/template-shadcn-ui.md | shadcn/ui integration |
| assets/component-libraries/template-mui-material-ui.md | MUI / Material UI |
| assets/interaction-patterns/template-micro-interactions.md | Micro-interactions |
Pattern Inspiration
- Mobbin — 300k+ screenshots
- Page Flows — User flow recordings
- Refero Design — Web design references
Related Skills
| Skill | Purpose |
|---|---|
| software-ux-research | Research (use first) |
| software-frontend | Implementation |
| software-mobile | Mobile patterns |
| product-management | Product strategy |
---
Fact-Checking
- Use web search/web fetch to verify current external facts, versions, pricing, deadlines, regulations, or platform behavior before final answers.
- Prefer primary sources; report source links and dates for volatile information.
- If web access is unavailable, state the limitation and mark guidance as unverified.
Accessibility Testing Template (WCAG 2.2 AA)
Use this template for ensuring web applications meet WCAG 2.2 Level AA accessibility standards.
Testing Tools Overview
axe-core - Best for:
- Automated accessibility audits
- CI/CD integration
- Catching common, automatable issues (not a replacement for manual testing)
- Component-level testing
Pa11y - Best for:
- Command-line accessibility testing
- Batch testing multiple URLs
- CI/CD integration
- Automated monitoring
Lighthouse - Best for:
- Overall accessibility score
- Chrome DevTools integration
- Performance + accessibility combo
- Quick initial audits
Manual Testing - Required for:
- Keyboard navigation
- Screen reader compatibility
- Color contrast edge cases
- Context and semantics (automation cannot judge intent)
Automated Testing with axe-core
Basic Component Testing
// components/Button.a11y.test.ts
import { render } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'
import { Button } from './Button'
expect.extend(toHaveNoViolations)
describe('Button Accessibility', () => {
it('should have no accessibility violations', async () => {
const { container } = render(
<Button variant="primary">Click me</Button>
)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
it('should be accessible when disabled', async () => {
const { container } = render(
<Button disabled>Disabled button</Button>
)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
it('should have proper ARIA attributes', async () => {
const { container } = render(
<Button aria-label="Close dialog" aria-pressed="true">
<span aria-hidden="true">×</span>
</Button>
)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
it('should be accessible with icon only', async () => {
const { container } = render(
<Button aria-label="Search">
<SearchIcon aria-hidden="true" />
</Button>
)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
})Form Accessibility Testing
// components/Form.a11y.test.ts
import { render } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'
import { LoginForm } from './LoginForm'
expect.extend(toHaveNoViolations)
describe('Form Accessibility', () => {
it('should have proper labels for inputs', async () => {
const { container } = render(<LoginForm />)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
it('should associate error messages with inputs', async () => {
const { container } = render(
<div>
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error"
/>
<span id="email-error" role="alert">
Email is required
</span>
</div>
)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
it('should use fieldset for grouped inputs', async () => {
const { container } = render(
<fieldset>
<legend>Payment Method</legend>
<label>
<input type="radio" name="payment" value="card" />
Credit Card
</label>
<label>
<input type="radio" name="payment" value="paypal" />
PayPal
</label>
</fieldset>
)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
})Full Page Testing with Playwright
// e2e/accessibility.test.ts
import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
test.describe('Page Accessibility', () => {
test('homepage should be accessible', async ({ page }) => {
await page.goto('/')
const accessibilityScanResults = await new AxeBuilder({ page }).analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
test('dashboard should be accessible', async ({ page }) => {
await page.goto('/dashboard')
await page.waitForLoadState('networkidle')
const accessibilityScanResults = await new AxeBuilder({ page })
.exclude('#third-party-widget') // Exclude third-party content
.analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
test('should check specific WCAG rules', async ({ page }) => {
await page.goto('/contact')
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']) // WCAG 2.2 AA (plus earlier tags)
.analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
test('should test specific regions', async ({ page }) => {
await page.goto('/dashboard')
const accessibilityScanResults = await new AxeBuilder({ page })
.include('[role="main"]') // Only test main content
.analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
})Manual Keyboard Navigation Testing
// e2e/keyboard-navigation.test.ts
import { test, expect } from '@playwright/test'
test.describe('Keyboard Navigation', () => {
test('should navigate form with Tab key', async ({ page }) => {
await page.goto('/contact')
// Tab through form fields
await page.keyboard.press('Tab')
await expect(page.locator('[name="name"]')).toBeFocused()
await page.keyboard.press('Tab')
await expect(page.locator('[name="email"]')).toBeFocused()
await page.keyboard.press('Tab')
await expect(page.locator('[name="message"]')).toBeFocused()
await page.keyboard.press('Tab')
await expect(page.locator('button[type="submit"]')).toBeFocused()
})
test('should navigate backwards with Shift+Tab', async ({ page }) => {
await page.goto('/contact')
// Focus submit button
await page.locator('button[type="submit"]').focus()
// Shift+Tab backwards
await page.keyboard.press('Shift+Tab')
await expect(page.locator('[name="message"]')).toBeFocused()
await page.keyboard.press('Shift+Tab')
await expect(page.locator('[name="email"]')).toBeFocused()
})
test('should submit form with Enter key', async ({ page }) => {
await page.goto('/contact')
await page.fill('[name="name"]', 'Test User')
await page.fill('[name="email"]', 'test@example.com')
await page.fill('[name="message"]', 'Test message')
await page.keyboard.press('Enter')
await expect(page.locator('.success-message')).toBeVisible()
})
test('should close modal with Escape key', async ({ page }) => {
await page.goto('/dashboard')
// Open modal
await page.click('[data-testid="open-modal"]')
await expect(page.locator('[role="dialog"]')).toBeVisible()
// Close with Escape
await page.keyboard.press('Escape')
await expect(page.locator('[role="dialog"]')).not.toBeVisible()
})
test('should trap focus in modal', async ({ page }) => {
await page.goto('/dashboard')
await page.click('[data-testid="open-modal"]')
const modal = page.locator('[role="dialog"]')
await expect(modal).toBeVisible()
// Focus should start on close button
await expect(modal.locator('button[aria-label="Close"]')).toBeFocused()
// Tab through modal elements
await page.keyboard.press('Tab')
await expect(modal.locator('input[name="name"]')).toBeFocused()
await page.keyboard.press('Tab')
await expect(modal.locator('button[type="submit"]')).toBeFocused()
// Tab should cycle back to close button (focus trap)
await page.keyboard.press('Tab')
await expect(modal.locator('button[aria-label="Close"]')).toBeFocused()
})
test('should navigate menu with arrow keys', async ({ page }) => {
await page.goto('/')
// Open dropdown menu
await page.click('[aria-haspopup="true"]')
const menu = page.locator('[role="menu"]')
await expect(menu).toBeVisible()
// Arrow down to first item
await page.keyboard.press('ArrowDown')
await expect(menu.locator('[role="menuitem"]').first()).toBeFocused()
// Arrow down to second item
await page.keyboard.press('ArrowDown')
await expect(menu.locator('[role="menuitem"]').nth(1)).toBeFocused()
// Arrow up back to first
await page.keyboard.press('ArrowUp')
await expect(menu.locator('[role="menuitem"]').first()).toBeFocused()
})
})Color Contrast Testing
// e2e/color-contrast.test.ts
import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
test.describe('Color Contrast (WCAG AA)', () => {
test('should meet contrast requirements', async ({ page }) => {
await page.goto('/')
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['cat.color']) // Only color contrast rules
.analyze()
// Filter for contrast violations
const contrastViolations = accessibilityScanResults.violations.filter(
v => v.id === 'color-contrast'
)
expect(contrastViolations).toEqual([])
})
test('should test contrast in dark mode', async ({ page }) => {
await page.goto('/')
// Enable dark mode
await page.evaluate(() => {
document.documentElement.setAttribute('data-theme', 'dark')
})
await page.waitForTimeout(300)
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['cat.color'])
.analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
test('should validate button contrast', async ({ page }) => {
await page.goto('/components/button')
const accessibilityScanResults = await new AxeBuilder({ page })
.include('[data-testid="button-showcase"]')
.withTags(['cat.color'])
.analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
})Screen Reader Testing (Automated)
// e2e/screen-reader.test.ts
import { test, expect } from '@playwright/test'
test.describe('Screen Reader Compatibility', () => {
test('should have proper heading hierarchy', async ({ page }) => {
await page.goto('/about')
// Check h1 exists and is unique
const h1Count = await page.locator('h1').count()
expect(h1Count).toBe(1)
// Check heading order
const headings = await page.locator('h1, h2, h3, h4, h5, h6').allTextContents()
// Validate no heading levels are skipped
const levels = await page.locator('h1, h2, h3, h4, h5, h6').evaluateAll(elements =>
elements.map(el => parseInt(el.tagName.substring(1)))
)
for (let i = 1; i < levels.length; i++) {
expect(levels[i] - levels[i - 1]).toBeLessThanOrEqual(1)
}
})
test('should use semantic landmarks', async ({ page }) => {
await page.goto('/')
// Check for semantic HTML5 landmarks
await expect(page.locator('header')).toBeVisible()
await expect(page.locator('nav')).toBeVisible()
await expect(page.locator('main')).toBeVisible()
await expect(page.locator('footer')).toBeVisible()
})
test('should have alt text for images', async ({ page }) => {
await page.goto('/gallery')
const images = await page.locator('img').all()
for (const img of images) {
const alt = await img.getAttribute('alt')
const ariaLabel = await img.getAttribute('aria-label')
const role = await img.getAttribute('role')
// Image must have alt text, or be decorative (role="presentation")
const isAccessible = alt !== null || ariaLabel !== null || role === 'presentation'
expect(isAccessible).toBe(true)
}
})
test('should announce dynamic content updates', async ({ page }) => {
await page.goto('/dashboard')
// Check for live regions
const liveRegion = page.locator('[aria-live="polite"]')
await expect(liveRegion).toBeVisible()
// Trigger update
await page.click('[data-testid="refresh-data"]')
// Verify live region content updates
await expect(liveRegion).toContainText('Data updated')
})
test('should provide text alternatives for icons', async ({ page }) => {
await page.goto('/')
const iconButtons = await page.locator('button:has(svg)').all()
for (const button of iconButtons) {
const ariaLabel = await button.getAttribute('aria-label')
const title = await button.getAttribute('title')
const textContent = await button.textContent()
// Button must have accessible name
const hasAccessibleName =
(ariaLabel && ariaLabel.length > 0) ||
(title && title.length > 0) ||
(textContent && textContent.trim().length > 0)
expect(hasAccessibleName).toBe(true)
}
})
})ARIA Attributes Testing
// components/Modal.a11y.test.ts
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Modal } from './Modal'
describe('Modal ARIA Attributes', () => {
it('should have proper dialog role', () => {
render(
<Modal isOpen onClose={() => {}}>
<h2>Dialog Title</h2>
<p>Dialog content</p>
</Modal>
)
const dialog = screen.getByRole('dialog')
expect(dialog).toBeInTheDocument()
})
it('should have aria-modal attribute', () => {
render(
<Modal isOpen onClose={() => {}}>
<h2>Dialog Title</h2>
</Modal>
)
const dialog = screen.getByRole('dialog')
expect(dialog).toHaveAttribute('aria-modal', 'true')
})
it('should have aria-labelledby pointing to title', () => {
render(
<Modal isOpen onClose={() => {}}>
<h2 id="modal-title">Dialog Title</h2>
</Modal>
)
const dialog = screen.getByRole('dialog')
expect(dialog).toHaveAttribute('aria-labelledby', 'modal-title')
})
it('should have aria-describedby for description', () => {
render(
<Modal isOpen onClose={() => {}}>
<h2 id="modal-title">Dialog Title</h2>
<p id="modal-description">This is the dialog description</p>
</Modal>
)
const dialog = screen.getByRole('dialog')
expect(dialog).toHaveAttribute('aria-describedby', 'modal-description')
})
})Accessibility Testing Checklist
Automated Tests (axe-core)
- [ ] Run axe-core on all pages
- [ ] Test all interactive components (buttons, forms, modals)
- [ ] Test color contrast (normal and dark mode)
- [ ] Verify no duplicate IDs
- [ ] Check image alt text
- [ ] Validate ARIA attributes
- [ ] Test heading hierarchy
Keyboard Navigation
- [ ] Tab through all interactive elements
- [ ] Shift+Tab backwards navigation works
- [ ] Enter/Space activates buttons and links
- [ ] Arrow keys navigate menus and lists
- [ ] Escape closes modals and dropdowns
- [ ] Focus is visible at all times
- [ ] Focus trapped in modals
- [ ] Skip to main content link works
Screen Reader Testing (Manual)
- [ ] Test with NVDA (Windows) or VoiceOver (macOS)
- [ ] Headings announce correctly
- [ ] Landmarks identified properly
- [ ] Form labels read with inputs
- [ ] Error messages announced
- [ ] Dynamic content updates announced (aria-live)
- [ ] Images have descriptive alt text
- [ ] Links have descriptive text (no "click here")
Color and Visual
- [ ] Text contrast ratio ≥ 4.5:1 (normal text)
- [ ] Text contrast ratio ≥ 3:1 (large text 18pt+)
- [ ] UI component contrast ≥ 3:1
- [ ] Focus indicators visible (3:1 contrast)
- [ ] Information not conveyed by color alone
- [ ] Text resizable up to 200% without loss of functionality
- [ ] Content readable without horizontal scrolling
Forms
- [ ] All inputs have associated labels
- [ ] Required fields marked with aria-required or required
- [ ] Error messages associated with inputs (aria-describedby)
- [ ] Error messages use role="alert" for announcements
- [ ] Fieldsets used for grouped inputs (radio, checkbox)
- [ ] Autocomplete attributes used appropriately
CI/CD Integration
GitHub Actions
# .github/workflows/accessibility.yml
name: Accessibility Tests
on:
pull_request:
branches: [main]
jobs:
a11y-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Run accessibility tests
run: npm run test:a11y
- name: Upload axe results
if: failure()
uses: actions/upload-artifact@v3
with:
name: axe-violations
path: axe-results.jsonPa11y CI Configuration
// .pa11yci.json
{
"defaults": {
"standard": "WCAG2AA",
"runners": ["axe", "htmlcs"],
"timeout": 30000,
"chromeLaunchConfig": {
"args": ["--no-sandbox"]
}
},
"urls": [
"http://localhost:3000/",
"http://localhost:3000/about",
"http://localhost:3000/contact",
{
"url": "http://localhost:3000/dashboard",
"actions": [
"set field #username to testuser",
"set field #password to password",
"click element button[type='submit']",
"wait for url to be http://localhost:3000/dashboard"
]
}
]
}Best Practices
- [ ] Test accessibility early in development (shift-left)
- [ ] Combine automated tools with manual testing
- [ ] Test with real assistive technologies
- [ ] Include users with disabilities in user testing
- [ ] Document accessibility features
- [ ] Train team on WCAG standards
- [ ] Make accessibility part of Definition of Done
- [ ] Monitor accessibility in production
Common Violations and Fixes
Missing Alt Text
// Bad
<img src="logo.png" />
// Good
<img src="logo.png" alt="Company Logo" />
// Decorative image
<img src="decoration.png" alt="" role="presentation" />Poor Form Labels
// Bad
<input type="text" placeholder="Email" />
// Good
<label htmlFor="email">Email</label>
<input id="email" type="text" />Low Contrast
/* Bad - 3:1 contrast */
.text {
color: #757575;
background: #ffffff;
}
/* Good - 4.6:1 contrast */
.text {
color: #595959;
background: #ffffff;
}Non-Semantic Buttons
// Bad
<div onClick={handleClick}>Click me</div>
// Good
<button onClick={handleClick}>Click me</button>Related Resources
CRO Audit Template
Comprehensive conversion rate optimization audit framework.
---
Audit Information
| Field | Value |
|---|---|
| Site/App | [Name] |
| Audit Date | [Date] |
| Auditor | [Name] |
| Scope | [Full site / Specific flow / Single page] |
| Primary Conversion | [e.g., Purchase, Signup, Lead] |
---
Executive Summary
Overall CRO Score: [X/100]
Critical Issues (Fix Immediately)
1. [Issue] - [Location] - [Impact] 2. [Issue] - [Location] - [Impact]
High Priority (This Sprint)
1. [Issue] - [Location] - [Impact] 2. [Issue] - [Location] - [Impact]
Medium Priority (Next Sprint)
1. [Issue] - [Location] - [Impact]
Quick Wins (Easy Fixes)
1. [Issue] - [Location] - [Est. effort]
---
Current Metrics Baseline
| Metric | Current | Benchmark | Gap |
|---|---|---|---|
| Overall Conversion Rate | [X%] | [Y%] | [+/-Z%] |
| Cart Abandonment | [X%] | [Y%] | [+/-Z%] |
| Bounce Rate | [X%] | [Y%] | [+/-Z%] |
| Avg. Order Value | [$X] | [$Y] | [+/-$Z] |
| Pages/Session | [X] | [Y] | [+/-Z] |
---
Homepage Audit
Above the Fold
| Element | Score | Issue | Recommendation |
|---|---|---|---|
| Value Proposition | [1-5] | [Issue or "OK"] | [Recommendation] |
| Primary CTA | [1-5] | [Issue or "OK"] | [Recommendation] |
| Trust Signals | [1-5] | [Issue or "OK"] | [Recommendation] |
| Navigation | [1-5] | [Issue or "OK"] | [Recommendation] |
| Search | [1-5] | [Issue or "OK"] | [Recommendation] |
| Load Time | [1-5] | [Xs] | [Target <3s] |
Below the Fold
| Element | Score | Issue | Recommendation |
|---|---|---|---|
| Social Proof | [1-5] | ||
| Feature Highlights | [1-5] | ||
| Secondary CTAs | [1-5] | ||
| Footer | [1-5] |
---
Product/Service Pages Audit
Page: [Name]
| Element | Score | Issue | Recommendation |
|---|---|---|---|
| Images/Media | [1-5] | ||
| Title/Headline | [1-5] | ||
| Price Display | [1-5] | ||
| Description | [1-5] | ||
| Add to Cart CTA | [1-5] | ||
| Reviews/Ratings | [1-5] | ||
| Related Products | [1-5] | ||
| Trust Signals | [1-5] | ||
| Mobile UX | [1-5] |
---
Checkout Flow Audit
Step: Cart
| Element | Score | Issue | Recommendation |
|---|---|---|---|
| Cart Summary | [1-5] | ||
| Price Transparency | [1-5] | ||
| Edit Capability | [1-5] | ||
| Proceed CTA | [1-5] | ||
| Cross-sell/Upsell | [1-5] | ||
| Save for Later | [1-5] |
Step: Shipping/Information
| Element | Score | Issue | Recommendation |
|---|---|---|---|
| Guest Checkout | [1-5] | ||
| Form Length | [1-5] | ||
| Address Autocomplete | [1-5] | ||
| Delivery Options | [1-5] | ||
| Delivery Estimates | [1-5] | ||
| Progress Indicator | [1-5] |
Step: Payment
| Element | Score | Issue | Recommendation |
|---|---|---|---|
| Payment Methods | [1-5] | ||
| Express Checkout | [1-5] | ||
| Security Badges | [1-5] | ||
| Form Validation | [1-5] | ||
| Error Messages | [1-5] |
Step: Confirmation
| Element | Score | Issue | Recommendation |
|---|---|---|---|
| Order Summary | [1-5] | ||
| Next Steps | [1-5] | ||
| Account Creation | [1-5] | ||
| Cross-sell | [1-5] |
---
Trust & Credibility Audit
| Element | Present | Location | Quality |
|---|---|---|---|
| SSL Certificate | [Y/N] | [Location] | [1-5] |
| Privacy Policy | [Y/N] | [Location] | [1-5] |
| Return Policy | [Y/N] | [Location] | [1-5] |
| Contact Info | [Y/N] | [Location] | [1-5] |
| Customer Reviews | [Y/N] | [Location] | [1-5] |
| Trust Badges | [Y/N] | [Location] | [1-5] |
| Social Proof | [Y/N] | [Location] | [1-5] |
| Company Info | [Y/N] | [Location] | [1-5] |
---
Mobile Experience Audit
| Element | Score | Issue | Recommendation |
|---|---|---|---|
| Touch Targets | [1-5] | [Min 44px?] | |
| Thumb-friendly | [1-5] | [CTAs in reach zone?] | |
| Form Input | [1-5] | [Keyboard types?] | |
| Viewport | [1-5] | [No horizontal scroll?] | |
| Speed | [1-5] | [Mobile load time] | |
| Navigation | [1-5] | [Easy to find?] | |
| Search | [1-5] | [Accessible?] | |
| Checkout | [1-5] | [Simplified?] |
---
Psychology Principles Assessment
| Principle | Present | Effectiveness | Recommendation |
|---|---|---|---|
| Scarcity | [Y/N] | [1-5] | |
| Urgency | [Y/N] | [1-5] | |
| Social Proof | [Y/N] | [1-5] | |
| Authority | [Y/N] | [1-5] | |
| Reciprocity | [Y/N] | [1-5] | |
| Commitment | [Y/N] | [1-5] |
---
Technical Performance Audit
| Metric | Current | Target | Status |
|---|---|---|---|
| LCP (Desktop) | [Xs] | <2.5s | [Pass/Fail] |
| LCP (Mobile) | [Xs] | <2.5s | [Pass/Fail] |
| INP | [Xms] | <200ms | [Pass/Fail] |
| CLS | [X] | <0.1 | [Pass/Fail] |
| TTFB | [Xms] | <800ms | [Pass/Fail] |
| Page Size | [X MB] | <3MB | [Pass/Fail] |
---
Funnel Analysis
Drop-off Points
| Funnel Stage | Entry | Exit | Drop-off % | Issues |
|---|---|---|---|---|
| Homepage | [X] | [Y] | [Z%] | |
| Product View | [X] | [Y] | [Z%] | |
| Add to Cart | [X] | [Y] | [Z%] | |
| Checkout Start | [X] | [Y] | [Z%] | |
| Shipping | [X] | [Y] | [Z%] | |
| Payment | [X] | [Y] | [Z%] | |
| Confirmation | [X] | [Y] | [Z%] |
---
Recommendations Summary
Immediate Actions (Week 1)
| # | Recommendation | Location | Impact | Effort |
|---|---|---|---|---|
| 1 | [H/M/L] | [H/M/L] | ||
| 2 | [H/M/L] | [H/M/L] |
Short-term (Month 1)
| # | Recommendation | Location | Impact | Effort |
|---|---|---|---|---|
| 1 | [H/M/L] | [H/M/L] | ||
| 2 | [H/M/L] | [H/M/L] |
Test Hypotheses
| Hypothesis | Page | Expected Impact | Priority |
|---|---|---|---|
| [1-5] | |||
| [1-5] |
---
Appendix
Tools Used
- [Analytics tool: e.g., GA4]
- [Heatmap: e.g., Hotjar]
- [Speed: e.g., PageSpeed Insights]
Screenshots/Evidence
[Link to folder with screenshots]
Competitor Comparison
| Competitor | Strengths | Weaknesses |
|---|---|---|
Simplification Audit Template
Score each section 1-5. Total score determines overall simplification readiness.
Scoring: 1 = Fails for non-tech users | 3 = Acceptable with friction | 5 = Optimized for low digital literacy
---
Product / Flow Audited
- Product: ___
- Flow: ___
- Date: ___
- Auditor: ___
- Target tier: Excluded / Dependent / Hesitant / Capable
---
Section 1: Navigation (Score: __/5)
| Criteria | Y/N | Notes |
|---|---|---|
| IA depth is 3 levels or fewer | ||
| Primary navigation is always visible (not hidden in hamburger) | ||
| All navigation items have text labels (not icon-only) | ||
| Menu has 7 or fewer items per level | ||
| Back/Home/Cancel always accessible | ||
| Breadcrumbs or step indicators present for multi-step flows | ||
| No nested dropdown menus |
---
Section 2: Language (Score: __/5)
| Criteria | Y/N | Notes |
|---|---|---|
| Reading level is Grade 8 or below (Hemingway test) | ||
| No technical jargon without explanation | ||
| Button labels describe actions ("Save changes" not "Submit") | ||
| Error messages explain what happened + what to do | ||
| Help text is available inline (not just in docs) | ||
| Confirmation messages use positive language |
---
Section 3: Interaction (Score: __/5)
| Criteria | Y/N | Notes |
|---|---|---|
| Touch targets are 44px+ (48px+ preferred) | ||
| One primary CTA per screen is visually dominant | ||
| Form validation is helpful, not punitive | ||
| Drag-and-drop has a non-drag alternative | ||
| Hover-dependent features have tap/click alternatives | ||
| Destructive actions have undo or confirmation | ||
| Loading states show progress or skeleton |
---
Section 4: Onboarding (Score: __/5)
| Criteria | Y/N | Notes |
|---|---|---|
| First-run shows 3 or fewer starting actions | ||
| No feature tour longer than 3 steps | ||
| Suggested actions/prompts reduce blank-state anxiety | ||
| Account setup requires minimal information | ||
| Value is shown before sign-up is required | ||
| Return visits don't re-trigger onboarding |
---
Section 5: Cognitive Load (Score: __/5)
| Criteria | Y/N | Notes |
|---|---|---|
| 5 or fewer choices per screen (primary actions) | ||
| Advanced features hidden behind "More" or settings | ||
| Confirmation step before irreversible actions | ||
| Related information is grouped visually | ||
| Whitespace is used to separate sections | ||
| Animations respect prefers-reduced-motion | ||
| No auto-playing media |
---
Score Summary
| Section | Score (1-5) | Priority Findings |
|---|---|---|
| Navigation | ||
| Language | ||
| Interaction | ||
| Onboarding | ||
| Cognitive Load | ||
| Total | /25 |
Rating
| Total Score | Rating | Recommendation |
|---|---|---|
| 21-25 | Optimized | Minor refinements only |
| 16-20 | Good | Address specific gaps |
| 11-15 | Needs work | Targeted simplification sprint |
| 6-10 | Poor | Major redesign for non-tech users |
| 1-5 | Critical | Not usable by non-technical users |
---
Top 3 Findings
1. Finding: ___ | Severity: Critical / Major / Minor | Section: ___ 2. Finding: ___ | Severity: Critical / Major / Minor | Section: ___ 3. Finding: ___ | Severity: Critical / Major / Minor | Section: ___
Recommended Next Steps
- [ ] ___
- [ ] ___
- [ ] ___
Material-UI (MUI) Component Library Template
Material-UI (MUI) is the most popular React UI library with 95k+ GitHub stars and 4.1M+ weekly downloads. It implements Google's Material Design system with a comprehensive set of pre-built, customizable React components for building modern web applications.
---
Key Characteristics
Architecture: Full-featured NPM package with extensive component library Styling: Emotion-based CSS-in-JS with sx prop and styled API Design System: Google Material Design 3 (M3) principles TypeScript: Complete TypeScript support with strict types Customization: Theme customization via theme provider Accessibility: Strong a11y defaults; verify WCAG 2.2 AA at the app level
Best for:
- Enterprise applications
- Teams familiar with Material Design
- Projects requiring comprehensive out-of-the-box components
- Rapid prototyping with minimal custom styling
Trusted by: Spotify, Amazon, Netflix, NASA, Unity
---
Installation & Setup
Core Package
npm install @mui/material @emotion/react @emotion/styledOptional Packages
# Icons
npm install @mui/icons-material
# Data Grid (advanced tables)
npm install @mui/x-data-grid
# Date pickers
npm install @mui/x-date-pickers dayjs
# Charts
npm install @mui/x-chartsFont Setup
<!-- Add to <head> in your HTML -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>---
Project Structure
src/
├── components/
│ ├── common/ # Reusable MUI-wrapped components
│ ├── layouts/ # Page layouts
│ └── pages/ # Page components
├── theme/
│ ├── theme.ts # MUI theme configuration
│ └── overrides.ts # Component style overrides
├── App.tsx
└── index.tsx---
Theme Configuration
Basic Theme Setup
// theme/theme.ts
import { createTheme } from '@mui/material/styles'
export const theme = createTheme({
palette: {
mode: 'light',
primary: {
main: '#1976d2',
light: '#42a5f5',
dark: '#1565c0',
contrastText: '#fff',
},
secondary: {
main: '#9c27b0',
light: '#ba68c8',
dark: '#7b1fa2',
contrastText: '#fff',
},
error: {
main: '#d32f2f',
},
warning: {
main: '#ed6c02',
},
info: {
main: '#0288d1',
},
success: {
main: '#2e7d32',
},
},
typography: {
fontFamily: [
'Roboto',
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Arial',
'sans-serif',
].join(','),
h1: {
fontSize: '2.5rem',
fontWeight: 500,
},
h2: {
fontSize: '2rem',
fontWeight: 500,
},
body1: {
fontSize: '1rem',
lineHeight: 1.5,
},
},
spacing: 8, // Default spacing unit (8px)
shape: {
borderRadius: 4, // Default border radius
},
})
// App.tsx
import { ThemeProvider } from '@mui/material/styles'
import CssBaseline from '@mui/material/CssBaseline'
import { theme } from './theme/theme'
function App() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{/* Your app content */}
</ThemeProvider>
)
}Dark Mode Support
import { useState, useMemo } from 'react'
import { createTheme, ThemeProvider } from '@mui/material/styles'
import { PaletteMode } from '@mui/material'
import CssBaseline from '@mui/material/CssBaseline'
import IconButton from '@mui/material/IconButton'
import Brightness4Icon from '@mui/icons-material/Brightness4'
import Brightness7Icon from '@mui/icons-material/Brightness7'
function App() {
const [mode, setMode] = useState<PaletteMode>('light')
const theme = useMemo(
() =>
createTheme({
palette: {
mode,
...(mode === 'light'
? {
// Light mode colors
primary: { main: '#1976d2' },
background: { default: '#fff', paper: '#f5f5f5' },
}
: {
// Dark mode colors
primary: { main: '#90caf9' },
background: { default: '#121212', paper: '#1e1e1e' },
}),
},
}),
[mode]
)
const toggleColorMode = () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'))
}
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<IconButton onClick={toggleColorMode} color="inherit">
{mode === 'dark' ? <Brightness7Icon /> : <Brightness4Icon />}
</IconButton>
{/* Your app content */}
</ThemeProvider>
)
}---
Core Components Usage
Buttons
import Button from '@mui/material/Button'
import IconButton from '@mui/material/IconButton'
import DeleteIcon from '@mui/icons-material/Delete'
import LoadingButton from '@mui/lab/LoadingButton'
import SaveIcon from '@mui/icons-material/Save'
export function ButtonDemo() {
return (
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
{/* Variants */}
<Button variant="contained">Contained</Button>
<Button variant="outlined">Outlined</Button>
<Button variant="text">Text</Button>
{/* Colors */}
<Button variant="contained" color="primary">Primary</Button>
<Button variant="contained" color="secondary">Secondary</Button>
<Button variant="contained" color="error">Error</Button>
<Button variant="contained" color="success">Success</Button>
{/* Sizes */}
<Button variant="contained" size="small">Small</Button>
<Button variant="contained" size="medium">Medium</Button>
<Button variant="contained" size="large">Large</Button>
{/* With Icons */}
<Button variant="contained" startIcon={<DeleteIcon />}>
Delete
</Button>
{/* Icon Button */}
<IconButton color="primary" aria-label="delete">
<DeleteIcon />
</IconButton>
{/* Loading Button */}
<LoadingButton loading variant="outlined">
Submit
</LoadingButton>
<LoadingButton
loading
loadingPosition="start"
startIcon={<SaveIcon />}
variant="outlined"
>
Save
</LoadingButton>
{/* States */}
<Button variant="contained" disabled>Disabled</Button>
</div>
)
}Form Components
import TextField from '@mui/material/TextField'
import Select from '@mui/material/Select'
import MenuItem from '@mui/material/MenuItem'
import FormControl from '@mui/material/FormControl'
import InputLabel from '@mui/material/InputLabel'
import Checkbox from '@mui/material/Checkbox'
import FormControlLabel from '@mui/material/FormControlLabel'
import Radio from '@mui/material/Radio'
import RadioGroup from '@mui/material/RadioGroup'
import Switch from '@mui/material/Switch'
import Slider from '@mui/material/Slider'
export function FormDemo() {
const [age, setAge] = useState('')
return (
<Box component="form" sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxWidth: 400 }}>
{/* Text Field */}
<TextField
label="Email"
type="email"
placeholder="you@example.com"
required
helperText="We'll never share your email"
/>
{/* Text Field with Error */}
<TextField
error
label="Password"
type="password"
helperText="Password must be at least 8 characters"
/>
{/* Select Dropdown */}
<FormControl fullWidth>
<InputLabel id="age-label">Age</InputLabel>
<Select
labelId="age-label"
value={age}
label="Age"
onChange={(e) => setAge(e.target.value)}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
{/* Checkbox */}
<FormControlLabel
control={<Checkbox defaultChecked />}
label="I agree to the terms and conditions"
/>
{/* Radio Group */}
<FormControl>
<RadioGroup defaultValue="option1">
<FormControlLabel value="option1" control={<Radio />} label="Option 1" />
<FormControlLabel value="option2" control={<Radio />} label="Option 2" />
</RadioGroup>
</FormControl>
{/* Switch */}
<FormControlLabel control={<Switch defaultChecked />} label="Enable notifications" />
{/* Slider */}
<Slider
defaultValue={30}
valueLabelDisplay="auto"
step={10}
marks
min={0}
max={100}
/>
<Button variant="contained" type="submit">Submit</Button>
</Box>
)
}Card Component
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardMedia from '@mui/material/CardMedia'
import CardContent from '@mui/material/CardContent'
import CardActions from '@mui/material/CardActions'
import Avatar from '@mui/material/Avatar'
import IconButton from '@mui/material/IconButton'
import Typography from '@mui/material/Typography'
import FavoriteIcon from '@mui/icons-material/Favorite'
import ShareIcon from '@mui/icons-material/Share'
import MoreVertIcon from '@mui/icons-material/MoreVert'
export function CardDemo() {
return (
<Card sx={{ maxWidth: 345 }}>
<CardHeader
avatar={
<Avatar sx={{ bgcolor: 'red' }}>
R
</Avatar>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title="Shrimp and Chorizo Paella"
subheader="September 14, 2024"
/>
<CardMedia
component="img"
height="194"
image="/static/images/cards/paella.jpg"
alt="Paella dish"
/>
<CardContent>
<Typography variant="body2" color="text.secondary">
This impressive paella is a perfect party dish and a fun meal to cook
together with your guests.
</Typography>
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
</IconButton>
<IconButton aria-label="share">
<ShareIcon />
</IconButton>
</CardActions>
</Card>
)
}Dialog (Modal) Component
import Dialog from '@mui/material/Dialog'
import DialogActions from '@mui/material/DialogActions'
import DialogContent from '@mui/material/DialogContent'
import DialogContentText from '@mui/material/DialogContentText'
import DialogTitle from '@mui/material/DialogTitle'
import Button from '@mui/material/Button'
export function DialogDemo() {
const [open, setOpen] = useState(false)
const handleClickOpen = () => setOpen(true)
const handleClose = () => setOpen(false)
return (
<>
<Button variant="outlined" onClick={handleClickOpen}>
Open dialog
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
Use Google's location service?
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Let Google help apps determine location. This means sending anonymous
location data to Google, even when no apps are running.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Disagree</Button>
<Button onClick={handleClose} autoFocus>
Agree
</Button>
</DialogActions>
</Dialog>
</>
)
}---
The sx Prop (Styling)
MUI's sx prop provides a shorthand for styling components:
import Box from '@mui/material/Box'
<Box
sx={{
width: 300,
height: 300,
backgroundColor: 'primary.main',
'&:hover': {
backgroundColor: 'primary.dark',
opacity: [0.9, 0.8, 0.7],
},
// Responsive
fontSize: {
xs: 12, // 0-600px
sm: 14, // 600-960px
md: 16, // 960-1280px
lg: 18, // 1280-1920px
xl: 20, // 1920px+
},
// Spacing
p: 2, // padding: theme.spacing(2) = 16px
m: 1, // margin: theme.spacing(1) = 8px
mt: 3, // margin-top: 24px
mx: 'auto', // margin-left and margin-right: auto
}}
>
Content
</Box>---
Layout Components
Grid System (v2)
import Grid from '@mui/material/Grid2'
export function GridDemo() {
return (
<Grid container spacing={2}>
<Grid xs={12} md={8}>
<Item>xs=12 md=8</Item>
</Grid>
<Grid xs={12} md={4}>
<Item>xs=12 md=4</Item>
</Grid>
<Grid xs={6} md={4}>
<Item>xs=6 md=4</Item>
</Grid>
<Grid xs={6} md={4}>
<Item>xs=6 md=4</Item>
</Grid>
<Grid xs={6} md={4}>
<Item>xs=6 md=4</Item>
</Grid>
</Grid>
)
}Container & Stack
import Container from '@mui/material/Container'
import Stack from '@mui/material/Stack'
import Button from '@mui/material/Button'
export function LayoutDemo() {
return (
<Container maxWidth="lg">
{/* Vertical Stack */}
<Stack spacing={2}>
<Button variant="contained">Button 1</Button>
<Button variant="contained">Button 2</Button>
<Button variant="contained">Button 3</Button>
</Stack>
{/* Horizontal Stack */}
<Stack direction="row" spacing={2}>
<Button variant="outlined">Button 1</Button>
<Button variant="outlined">Button 2</Button>
</Stack>
</Container>
)
}---
Data Display Components
Data Grid (MUI X)
import { DataGrid, GridColDef } from '@mui/x-data-grid'
const columns: GridColDef[] = [
{ field: 'id', headerName: 'ID', width: 90 },
{ field: 'firstName', headerName: 'First name', width: 150 },
{ field: 'lastName', headerName: 'Last name', width: 150 },
{
field: 'age',
headerName: 'Age',
type: 'number',
width: 110,
},
]
const rows = [
{ id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 },
{ id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 },
]
export function DataGridDemo() {
return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
rows={rows}
columns={columns}
initialState={{
pagination: {
paginationModel: { page: 0, pageSize: 5 },
},
}}
pageSizeOptions={[5, 10]}
checkboxSelection
/>
</div>
)
}---
Form Validation (React Hook Form + MUI)
import { useForm, Controller } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import * as z from 'zod'
import TextField from '@mui/material/TextField'
import Button from '@mui/material/Button'
import Box from '@mui/material/Box'
const schema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
})
type FormData = z.infer<typeof schema>
export function LoginForm() {
const {
control,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(schema),
})
const onSubmit = (data: FormData) => {
console.log(data)
}
return (
<Box component="form" onSubmit={handleSubmit(onSubmit)} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Controller
name="email"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
{...field}
label="Email"
type="email"
error={!!errors.email}
helperText={errors.email?.message}
/>
)}
/>
<Controller
name="password"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
{...field}
label="Password"
type="password"
error={!!errors.password}
helperText={errors.password?.message}
/>
)}
/>
<Button type="submit" variant="contained">
Login
</Button>
</Box>
)
}---
Best Practices
1. Use Theme Spacing
// GOOD: Use theme spacing
<Box sx={{ p: 2, m: 1 }} />
// BAD: Hardcode pixels
<Box style={{ padding: '16px', margin: '8px' }} />2. Responsive Design
// GOOD: Use breakpoints
<Box sx={{ width: { xs: '100%', md: '50%' } }} />
// BAD: Fixed widths
<Box sx={{ width: '600px' }} />3. Accessibility
// GOOD: Proper ARIA labels
<IconButton aria-label="delete">
<DeleteIcon />
</IconButton>
// BAD: No label
<IconButton>
<DeleteIcon />
</IconButton>4. Component Composition
// GOOD: Compose with MUI components
<Card>
<CardHeader title="Title" />
<CardContent>Content</CardContent>
</Card>
// BAD: Build everything from scratch
<div className="card">
<div className="header">Title</div>
<div className="content">Content</div>
</div>---
Resources
- Official Docs: https://mui.com/
- Component API: https://mui.com/material-ui/api/button/
- Templates: https://mui.com/material-ui/getting-started/assets/
- Icons: https://mui.com/material-ui/material-icons/
- GitHub: https://github.com/mui/material-ui
---
Related Templates
template-ant-design.md— Enterprise UI library alternativetemplate-chakra-ui.md— Accessible React component library- template-design-system.md — Design system template
shadcn/ui Component Library Template
shadcn/ui is a collection of beautifully designed, accessible, and customizable React components built with Radix UI and Tailwind CSS. Unlike traditional UI libraries, shadcn/ui is not installed as a dependency—components are copied directly into your project for full ownership and customization.
---
Key Characteristics
Architecture: Copy-paste component system (not an NPM package) Styling: Tailwind CSS with CSS variables for theming Primitives: Built on Radix UI (accessible, unstyled components) TypeScript: Full TypeScript support with type safety Customization: Full component ownership—modify as needed Accessibility: Strong a11y defaults; verify WCAG 2.2 AA at the app level
Best for:
- Projects requiring design flexibility
- Teams that want component ownership
- Tailwind CSS users
- Modern React applications with Next.js/Vite
---
Installation & Setup
Prerequisites
# Ensure you have a React project with Tailwind CSS
npm install tailwindcss
npx tailwindcss init -pInitialize shadcn/ui
npx shadcn-ui@latest initConfiguration prompts:
- Would you like to use TypeScript? Yes
- Which style would you like to use? Default / New York
- Which color would you like to use as base color? Slate / Zinc / Neutral
- Where is your global CSS file? src/app/globals.css
- Would you like to use CSS variables for colors? Yes (recommended)
- Where is your tailwind.config.js? tailwind.config.ts
- Configure the import alias for components? @/components
- Configure the import alias for utils? @/lib/utils
Add Components
# Add individual components
npx shadcn-ui@latest add button
npx shadcn-ui@latest add input
npx shadcn-ui@latest add card
# Add multiple components at once
npx shadcn-ui@latest add button input card dialog---
Project Structure
src/
├── components/
│ └── ui/ # shadcn/ui components (copied)
│ ├── button.tsx
│ ├── input.tsx
│ ├── card.tsx
│ ├── dialog.tsx
│ └── ...
├── lib/
│ └── utils.ts # cn() utility for class merging
├── app/
│ ├── globals.css # Tailwind + CSS variables
│ └── layout.tsx
└── tailwind.config.ts # Tailwind configuration---
Core Components Usage
Button Component
import { Button } from "@/components/ui/button"
export function ButtonDemo() {
return (
<div className="space-x-2">
{/* Variants */}
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
{/* Sizes */}
<Button size="sm">Small</Button>
<Button size="default">Default</Button>
<Button size="lg">Large</Button>
<Button size="icon">
<Icons.plus className="h-4 w-4" />
</Button>
{/* States */}
<Button disabled>Disabled</Button>
<Button loading>Loading</Button>
</div>
)
}Form Components
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export function FormDemo() {
return (
<form className="space-y-4">
{/* Text Input */}
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="you@example.com"
/>
</div>
{/* Select Dropdown */}
<div className="space-y-2">
<Label htmlFor="country">Country</Label>
<Select>
<SelectTrigger id="country">
<SelectValue placeholder="Select a country" />
</SelectTrigger>
<SelectContent>
<SelectItem value="us">United States</SelectItem>
<SelectItem value="uk">United Kingdom</SelectItem>
<SelectItem value="ca">Canada</SelectItem>
</SelectContent>
</Select>
</div>
{/* Checkbox */}
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Label htmlFor="terms">Accept terms and conditions</Label>
</div>
{/* Radio Group */}
<RadioGroup defaultValue="option-1">
<div className="flex items-center space-x-2">
<RadioGroupItem value="option-1" id="option-1" />
<Label htmlFor="option-1">Option 1</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="option-2" id="option-2" />
<Label htmlFor="option-2">Option 2</Label>
</div>
</RadioGroup>
<Button type="submit">Submit</Button>
</form>
)
}Card Component
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
export function CardDemo() {
return (
<Card className="w-[380px]">
<CardHeader>
<CardTitle>Create Project</CardTitle>
<CardDescription>Deploy your new project in one-click.</CardDescription>
</CardHeader>
<CardContent>
<form>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Name of your project" />
</div>
</div>
</form>
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline">Cancel</Button>
<Button>Deploy</Button>
</CardFooter>
</Card>
)
}Dialog (Modal) Component
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
export function DialogDemo() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Edit Profile</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Name
</Label>
<Input id="name" value="Pedro Duarte" className="col-span-3" />
</div>
</div>
<DialogFooter>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}---
Theming & Customization
CSS Variables (globals.css)
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
/* ... other dark mode variables */
}
}Customizing Components
Since components are copied into your project, you can modify them directly:
// components/ui/button.tsx
// Add custom variants
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md...",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground...",
// Add your custom variant
custom: "bg-gradient-to-r from-purple-500 to-pink-500 text-white...",
},
},
}
)
// Usage
<Button variant="custom">Custom Button</Button>---
Form Handling with React Hook Form + Zod
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
email: z.string().email({
message: "Please enter a valid email address.",
}),
})
export function ProfileForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
email: "",
},
})
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}---
Accessibility Features
shadcn/ui components are built on Radix UI, which provides:
- Keyboard navigation — Full keyboard support (Tab, Enter, Escape, Arrow keys)
- ARIA attributes — Proper aria-* attributes automatically applied
- Focus management — Focus trapping in modals, proper focus indicators
- Screen reader support — Descriptive labels and live regions
- Touch targets — Minimum 44x44px touch targets on mobile
Example: Dialog Accessibility
// Radix UI Dialog handles:
// - Focus trap (can't Tab outside modal)
// - Escape key to close
// - Click outside to close
// - aria-describedby for description
// - Focus restoration after close
// - Scroll locking---
Dark Mode Support
shadcn/ui uses CSS variables for easy dark mode:
// app/layout.tsx
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
)
}
// Theme toggle component
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
export function ThemeToggle() {
const { setTheme, theme } = useTheme()
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}---
Best Practices
1. Component Composition
// GOOD: Compose with smaller components
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
</CardHeader>
<CardContent>Content</CardContent>
</Card>
// BAD: Monolithic component
<Card title="Title" content="Content" />2. Use the cn() Utility
import { cn } from "@/lib/utils"
// Merge classes conditionally
<Button className={cn(
"base-classes",
isActive && "active-classes",
isPrimary && "primary-classes"
)} />3. Extend Components
// Create wrapper components for project-specific variants
import { Button } from "@/components/ui/button"
export function SubmitButton({ children, ...props }: ButtonProps) {
return (
<Button
type="submit"
className="w-full"
{...props}
>
{children}
</Button>
)
}4. Form Validation Pattern
// Always use Zod + React Hook Form for complex forms
// Built-in validation + type safety + accessibility---
Component Checklist
Essential shadcn/ui components to add:
Layout & Structure:
- [ ] Card
- [ ] Separator
- [ ] Aspect Ratio
- [ ] Scroll Area
Forms:
- [ ] Input
- [ ] Textarea
- [ ] Select
- [ ] Checkbox
- [ ] Radio Group
- [ ] Switch
- [ ] Slider
- [ ] Label
- [ ] Form
Feedback:
- [ ] Alert
- [ ] Alert Dialog
- [ ] Toast
- [ ] Progress
- [ ] Badge
Navigation:
- [ ] Command (Command palette)
- [ ] Menu (Dropdown menu)
- [ ] Navigation Menu
- [ ] Tabs
- [ ] Breadcrumb
Overlays:
- [ ] Dialog
- [ ] Sheet (Slide-over)
- [ ] Popover
- [ ] Tooltip
- [ ] Hover Card
Data Display:
- [ ] Table
- [ ] Avatar
- [ ] Calendar
- [ ] Accordion
---
Resources
- Official Docs: https://ui.shadcn.com/
- GitHub: https://github.com/shadcn-ui/ui
- Component Examples: https://ui.shadcn.com/examples
- Figma Kit: Available in community files
- Theme Generator: https://ui.shadcn.com/themes
---
Related Templates
template-radix-ui.md— Unstyled primitives (shadcn/ui's foundation)- template-design-system.md — Design system template
- template-wcag-testing.md — Accessibility testing
Design Brief Template
- Problem & goal: What user problem are we solving? Success metric?
- Audience & context: Personas, platform (web/mobile), environment constraints
- Scope: In/out of scope, dependencies, rollout phases, feature flags
- Experience principles: Tone, accessibility goals, brand constraints, motion guidelines
- Conceptual model: How the system should behave vs. how users think; mappings, signifiers, constraints
- Mental-model risks: Assumptions to validate, prior tools to match or reframe, terms that may confuse
- User journeys: Primary/edge flows, error/empty/loading states, recovery paths
- IA & navigation: Entry points, key screens, information hierarchy, breadcrumbs
- Content & microcopy: Voice, labels, CTA text, validation/error messaging rules
- Accessibility: Target WCAG level, keyboard support, color contrast, ARIA landmarks
- Success metrics: Task success/time, error/slip rate, first-click success, SUS/CSAT, guardrails
- Experimentation: A/B hypotheses, success metrics, guardrail metrics, telemetry needed
Design System Template
Use this template for building scalable, consistent design systems with tokens, components, and documentation.
Design System Architecture
design-system/
├── tokens/ # Design tokens (colors, spacing, typography)
│ ├── colors.json
│ ├── spacing.json
│ ├── typography.json
│ └── shadows.json
├── primitives/ # Base components (no business logic)
│ ├── Button/
│ ├── Input/
│ ├── Select/
│ └── Card/
├── patterns/ # Composite components
│ ├── Form/
│ ├── Navigation/
│ └── DataTable/
├── layouts/ # Layout primitives
│ ├── Stack/
│ ├── Grid/
│ └── Container/
├── utils/ # Utility functions
│ ├── classNames.ts
│ └── tokens.ts
└── docs/ # Storybook documentation
├── Introduction.stories.mdx
└── DesignPrinciples.stories.mdxDesign Tokens
Color Tokens (JSON format)
// tokens/colors.json
{
"color": {
"brand": {
"primary": {
"50": { "value": "#eff6ff", "type": "color" },
"100": { "value": "#dbeafe", "type": "color" },
"200": { "value": "#bfdbfe", "type": "color" },
"300": { "value": "#93c5fd", "type": "color" },
"400": { "value": "#60a5fa", "type": "color" },
"500": { "value": "#3b82f6", "type": "color" },
"600": { "value": "#2563eb", "type": "color" },
"700": { "value": "#1d4ed8", "type": "color" },
"800": { "value": "#1e40af", "type": "color" },
"900": { "value": "#1e3a8a", "type": "color" }
}
},
"semantic": {
"success": { "value": "{color.green.600}", "type": "color" },
"error": { "value": "{color.red.600}", "type": "color" },
"warning": { "value": "{color.yellow.600}", "type": "color" },
"info": { "value": "{color.blue.600}", "type": "color" }
},
"text": {
"primary": { "value": "{color.gray.900}", "type": "color" },
"secondary": { "value": "{color.gray.600}", "type": "color" },
"disabled": { "value": "{color.gray.400}", "type": "color" },
"inverse": { "value": "{color.white}", "type": "color" }
},
"background": {
"primary": { "value": "{color.white}", "type": "color" },
"secondary": { "value": "{color.gray.50}", "type": "color" },
"tertiary": { "value": "{color.gray.100}", "type": "color" }
},
"border": {
"default": { "value": "{color.gray.300}", "type": "color" },
"focus": { "value": "{color.brand.primary.500}", "type": "color" },
"error": { "value": "{color.semantic.error}", "type": "color" }
}
}
}Typography Tokens
// tokens/typography.json
{
"font": {
"family": {
"sans": {
"value": "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
"type": "fontFamily"
},
"mono": {
"value": "'Fira Code', 'Courier New', monospace",
"type": "fontFamily"
}
},
"size": {
"xs": { "value": "0.75rem", "type": "fontSize" },
"sm": { "value": "0.875rem", "type": "fontSize" },
"base": { "value": "1rem", "type": "fontSize" },
"lg": { "value": "1.125rem", "type": "fontSize" },
"xl": { "value": "1.25rem", "type": "fontSize" },
"2xl": { "value": "1.5rem", "type": "fontSize" },
"3xl": { "value": "1.875rem", "type": "fontSize" },
"4xl": { "value": "2.25rem", "type": "fontSize" }
},
"weight": {
"normal": { "value": "400", "type": "fontWeight" },
"medium": { "value": "500", "type": "fontWeight" },
"semibold": { "value": "600", "type": "fontWeight" },
"bold": { "value": "700", "type": "fontWeight" }
},
"lineHeight": {
"tight": { "value": "1.25", "type": "lineHeight" },
"normal": { "value": "1.5", "type": "lineHeight" },
"relaxed": { "value": "1.75", "type": "lineHeight" }
}
}
}Spacing Tokens
// tokens/spacing.json
{
"spacing": {
"0": { "value": "0", "type": "spacing" },
"1": { "value": "0.25rem", "type": "spacing" },
"2": { "value": "0.5rem", "type": "spacing" },
"3": { "value": "0.75rem", "type": "spacing" },
"4": { "value": "1rem", "type": "spacing" },
"5": { "value": "1.25rem", "type": "spacing" },
"6": { "value": "1.5rem", "type": "spacing" },
"8": { "value": "2rem", "type": "spacing" },
"10": { "value": "2.5rem", "type": "spacing" },
"12": { "value": "3rem", "type": "spacing" },
"16": { "value": "4rem", "type": "spacing" },
"20": { "value": "5rem", "type": "spacing" }
}
}Token Conversion to CSS Variables
// utils/tokens.ts
import colors from '../tokens/colors.json'
import spacing from '../tokens/spacing.json'
import typography from '../tokens/typography.json'
export function generateCSSVariables() {
const cssVars: Record<string, string> = {}
// Convert color tokens
function flattenTokens(obj: any, prefix = '') {
for (const [key, value] of Object.entries(obj)) {
if (value && typeof value === 'object' && 'value' in value) {
cssVars[`--${prefix}${key}`] = (value as any).value
} else if (typeof value === 'object') {
flattenTokens(value, `${prefix}${key}-`)
}
}
}
flattenTokens(colors.color, 'color-')
flattenTokens(spacing.spacing, 'spacing-')
flattenTokens(typography.font, 'font-')
return cssVars
}
// Export as CSS string
export function getCSSVariablesString() {
const vars = generateCSSVariables()
return Object.entries(vars)
.map(([key, value]) => ` ${key}: ${value};`)
.join('\n')
}Base Component: Button
// primitives/Button/Button.tsx
import React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '../../utils/classNames'
const buttonVariants = cva(
// Base styles
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-primary-600 text-white hover:bg-primary-700 focus-visible:ring-primary-500',
secondary: 'bg-secondary-600 text-white hover:bg-secondary-700 focus-visible:ring-secondary-500',
outline: 'border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 focus-visible:ring-primary-500',
ghost: 'text-gray-700 hover:bg-gray-100 focus-visible:ring-primary-500',
destructive: 'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500'
},
size: {
sm: 'h-9 px-3 text-sm',
md: 'h-10 px-4 text-base',
lg: 'h-11 px-6 text-lg'
},
fullWidth: {
true: 'w-full'
}
},
defaultVariants: {
variant: 'primary',
size: 'md'
}
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
loading?: boolean
leftIcon?: React.ReactNode
rightIcon?: React.ReactNode
}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{
className,
variant,
size,
fullWidth,
loading,
leftIcon,
rightIcon,
children,
disabled,
...props
},
ref
) => {
return (
<button
ref={ref}
className={cn(buttonVariants({ variant, size, fullWidth }), className)}
disabled={disabled || loading}
{...props}
>
{loading && (
<svg
className="mr-2 h-4 w-4 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
)}
{!loading && leftIcon && <span className="mr-2">{leftIcon}</span>}
{children}
{!loading && rightIcon && <span className="ml-2">{rightIcon}</span>}
</button>
)
}
)
Button.displayName = 'Button'Button Storybook Documentation
// primitives/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
import { SearchIcon, ArrowRightIcon } from 'lucide-react'
const meta: Meta<typeof Button> = {
title: 'Primitives/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'outline', 'ghost', 'destructive'],
description: 'Button visual style'
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
description: 'Button size'
},
loading: {
control: 'boolean',
description: 'Shows loading spinner'
},
disabled: {
control: 'boolean',
description: 'Disables button interaction'
}
}
}
export default meta
type Story = StoryObj<typeof Button>
export const Primary: Story = {
args: {
children: 'Button',
variant: 'primary'
}
}
export const AllVariants: Story = {
render: () => (
<div className="flex gap-4">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
</div>
)
}
export const AllSizes: Story = {
render: () => (
<div className="flex items-center gap-4">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</div>
)
}
export const WithIcons: Story = {
render: () => (
<div className="flex gap-4">
<Button leftIcon={<SearchIcon className="h-4 w-4" />}>
Search
</Button>
<Button rightIcon={<ArrowRightIcon className="h-4 w-4" />}>
Continue
</Button>
</div>
)
}
export const Loading: Story = {
args: {
children: 'Loading',
loading: true
}
}
export const Disabled: Story = {
args: {
children: 'Disabled',
disabled: true
}
}
export const FullWidth: Story = {
args: {
children: 'Full Width Button',
fullWidth: true
}
}Composite Pattern: Form
// patterns/Form/Form.tsx
import React from 'react'
import { cn } from '../../utils/classNames'
// Form Root
export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {}
export const Form = React.forwardRef<HTMLFormElement, FormProps>(
({ className, ...props }, ref) => {
return (
<form
ref={ref}
className={cn('space-y-6', className)}
{...props}
/>
)
}
)
Form.displayName = 'Form'
// Form Field
export interface FormFieldProps extends React.HTMLAttributes<HTMLDivElement> {
error?: string
}
export const FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(
({ className, error, children, ...props }, ref) => {
return (
<div ref={ref} className={cn('space-y-2', className)} {...props}>
{children}
{error && (
<p className="text-sm text-red-600" role="alert">
{error}
</p>
)}
</div>
)
}
)
FormField.displayName = 'FormField'
// Form Label
export interface FormLabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
required?: boolean
}
export const FormLabel = React.forwardRef<HTMLLabelElement, FormLabelProps>(
({ className, required, children, ...props }, ref) => {
return (
<label
ref={ref}
className={cn('block text-sm font-medium text-gray-700', className)}
{...props}
>
{children}
{required && <span className="ml-1 text-red-600" aria-label="required">*</span>}
</label>
)
}
)
FormLabel.displayName = 'FormLabel'
// Form Input
export interface FormInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
error?: boolean
}
export const FormInput = React.forwardRef<HTMLInputElement, FormInputProps>(
({ className, error, ...props }, ref) => {
return (
<input
ref={ref}
className={cn(
'block w-full rounded-md border border-gray-300 px-3 py-2 text-sm',
'focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500',
'disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500',
error && 'border-red-500 focus:border-red-500 focus:ring-red-500',
className
)}
{...props}
/>
)
}
)
FormInput.displayName = 'FormInput'
// Usage Example
export function ContactForm() {
return (
<Form>
<FormField>
<FormLabel htmlFor="name" required>
Name
</FormLabel>
<FormInput id="name" name="name" placeholder="John Doe" />
</FormField>
<FormField error="Invalid email address">
<FormLabel htmlFor="email" required>
Email
</FormLabel>
<FormInput
id="email"
name="email"
type="email"
placeholder="john@example.com"
error
/>
</FormField>
<FormField>
<FormLabel htmlFor="message">Message</FormLabel>
<textarea
id="message"
name="message"
rows={4}
className="block w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
/>
</FormField>
<Button type="submit">Submit</Button>
</Form>
)
}Layout Primitives: Stack
// layouts/Stack/Stack.tsx
import React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '../../utils/classNames'
const stackVariants = cva('flex', {
variants: {
direction: {
horizontal: 'flex-row',
vertical: 'flex-col'
},
spacing: {
0: 'gap-0',
1: 'gap-1',
2: 'gap-2',
3: 'gap-3',
4: 'gap-4',
6: 'gap-6',
8: 'gap-8'
},
align: {
start: 'items-start',
center: 'items-center',
end: 'items-end',
stretch: 'items-stretch'
},
justify: {
start: 'justify-start',
center: 'justify-center',
end: 'justify-end',
between: 'justify-between',
around: 'justify-around'
}
},
defaultVariants: {
direction: 'vertical',
spacing: 4,
align: 'stretch',
justify: 'start'
}
})
export interface StackProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof stackVariants> {}
export const Stack = React.forwardRef<HTMLDivElement, StackProps>(
({ className, direction, spacing, align, justify, ...props }, ref) => {
return (
<div
ref={ref}
className={cn(stackVariants({ direction, spacing, align, justify }), className)}
{...props}
/>
)
}
)
Stack.displayName = 'Stack'Documentation: Design Principles
<!-- docs/DesignPrinciples.stories.mdx -->
import { Meta } from '@storybook/blocks'
<Meta title="Introduction/Design Principles" />
# Design Principles
Our design system is built on four core principles:
## 1. Consistency
Consistent visual language across all products and platforms.
- Use design tokens for colors, spacing, and typography
- Follow established patterns for common interactions
- Maintain consistent naming conventions
## 2. Accessibility
Inclusive design that works for everyone.
- WCAG 2.2 AA compliance minimum
- Keyboard navigation support
- Screen reader compatible
- 4.5:1 contrast ratio for text
## 3. Flexibility
Adaptable components that work in various contexts.
- Composable component API
- Responsive design by default
- Theme customization support
- Dark mode compatibility
## 4. Developer Experience
Easy to use and well-documented.
- TypeScript support
- Comprehensive Storybook documentation
- Clear naming conventions
- Minimal API surface
## Component API Design
### Composition over Configuration
Prefer composition patterns:
// Good - Composition <Card> <CardHeader> <CardTitle>Title</CardTitle> </CardHeader> <CardContent>Content</CardContent> </Card>
// Avoid - Configuration <Card title="Title" content="Content" />
### Consistent Prop Naming
- `variant` for visual variations (primary, secondary, outline)
- `size` for size variations (sm, md, lg)
- `disabled` for disabled state
- `loading` for loading state
- `error` for error state
### Forwarded Refs
All components should forward refs:
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => { return <button ref={ref} {...props} /> })
Testing Design System Components
// primitives/Button/Button.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Button } from './Button'
describe('Button', () => {
it('renders correctly', () => {
render(<Button>Click me</Button>)
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument()
})
it('handles click events', async () => {
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click me</Button>)
await userEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('applies variant styles', () => {
const { rerender } = render(<Button variant="primary">Button</Button>)
expect(screen.getByRole('button')).toHaveClass('bg-primary-600')
rerender(<Button variant="outline">Button</Button>)
expect(screen.getByRole('button')).toHaveClass('border')
})
it('shows loading state', () => {
render(<Button loading>Loading</Button>)
expect(screen.getByRole('button')).toBeDisabled()
expect(screen.getByRole('button')).toContainHTML('svg') // Spinner
})
it('forwards ref', () => {
const ref = React.createRef<HTMLButtonElement>()
render(<Button ref={ref}>Button</Button>)
expect(ref.current).toBeInstanceOf(HTMLButtonElement)
})
})Versioning and Changelog
# Changelog
## [2.1.0] - 2024-01-15
### Added
- New `Toast` component for notifications
- `fullWidth` prop for `Button` component
- Dark mode support for all components
### Changed
- Updated focus ring styles to improve visibility
- Improved TypeScript types for better IDE support
### Fixed
- Fixed accessibility issue in `Modal` component
- Fixed z-index stacking in `Dropdown` component
### Breaking Changes
- Renamed `color` prop to `variant` in `Button` component
- Migration: Replace `color="primary"` with `variant="primary"`Best Practices Checklist
- [ ] Use design tokens for all design decisions
- [ ] Document all components in Storybook
- [ ] Include accessibility tests for all components
- [ ] Provide TypeScript types
- [ ] Forward refs in all components
- [ ] Use composition over configuration
- [ ] Test components in isolation
- [ ] Version design system packages
- [ ] Provide migration guides for breaking changes
- [ ] Support theme customization
- [ ] Include dark mode support
- [ ] Document component API clearly
- [ ] Provide code examples
Related Resources
Micro-interactions Design Template
Micro-interactions are small, functional animations that enhance user experience by providing feedback, guiding users, and making interfaces feel alive and responsive. In 2024-2025, micro-interactions are considered essential for modern UX design across both mobile and desktop platforms.
---
What Are Micro-interactions?
Definition: Small, subtle animations or visual responses triggered by user actions that communicate system status, provide feedback, or guide users through tasks.
Four Components (Dan Saffer Framework): 1. Trigger — What initiates the interaction (user action or system event) 2. Rules — What happens during the interaction (logic and constraints) 3. Feedback — How users perceive the interaction (visual, audio, haptic) 4. Loops & Modes — Ongoing behavior or state changes (repeat, duration, modes)
Purpose:
- Acknowledge user actions instantly
- Prevent errors through visual guidance
- Communicate system status (loading, processing, success/error)
- Delight users with polished, responsive UI
- Guide attention to important elements
---
Core Principles (2024 Best Practices)
1. Keep Them Simple and Purposeful
Every micro-interaction must serve a clear function—avoid decorative animations that distract or slow down task completion.
GOOD: Button changes color on hover to indicate interactivity BAD: Button rotates 360° with confetti animation just for hover
2. Provide Instant Feedback
Users expect immediate response (<100ms feels instant). Delayed feedback makes interfaces feel broken or unresponsive.
Response time perception:
- <100ms: Instant, feels like direct manipulation
- 100-300ms: Perceptible but still feels fast
- 300-1000ms: Noticeable delay, requires loading indicator
- >1000ms: User loses focus, needs progress indicator3. Design with Consistency
Use uniform timing, easing curves, and animation styles across the UI to create predictability and build user trust.
4. Use Timing Effectively
Animation duration affects perceived performance and polish:
- Too fast (<150ms): Jarring, hard to perceive
- Just right (200-400ms): Smooth, professional
- Too slow (>600ms): Sluggish, annoying
---
Common Micro-interaction Patterns
1. Button Interactions
Hover State
.button {
background-color: #007bff;
transition: all 0.2s ease-in-out;
}
.button:hover {
background-color: #0056b3;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}Active/Click State
.button:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.1s ease-out;
}Loading State
// React example with loading button
function LoadingButton({ loading, children, ...props }) {
return (
<button {...props} disabled={loading}>
{loading ? (
<>
<Spinner className="animate-spin" />
<span className="ml-2">Processing...</span>
</>
) : (
children
)}
</button>
)
}Success Animation
function SuccessButton() {
const [success, setSuccess] = useState(false)
const handleClick = async () => {
await submitForm()
setSuccess(true)
setTimeout(() => setSuccess(false), 2000)
}
return (
<button
onClick={handleClick}
className={success ? 'bg-green-500' : 'bg-blue-500'}
>
{success ? (
<>
<CheckIcon className="animate-check" />
<span>Success!</span>
</>
) : (
'Submit'
)}
</button>
)
}---
2. Form Input Interactions
Focus State with Label Animation
// Floating label pattern
import { useState } from 'react'
export function FloatingLabelInput({ label, ...props }) {
const [isFocused, setIsFocused] = useState(false)
const [hasValue, setHasValue] = useState(false)
return (
<div className="relative">
<input
{...props}
onFocus={() => setIsFocused(true)}
onBlur={(e) => {
setIsFocused(false)
setHasValue(e.target.value !== '')
}}
className="w-full px-4 pt-6 pb-2 border rounded-md"
/>
<label
className={`
absolute left-4 transition-all duration-200
${isFocused || hasValue
? 'top-2 text-xs text-blue-600'
: 'top-4 text-base text-gray-500'
}
`}
>
{label}
</label>
</div>
)
}Real-time Validation Feedback
export function EmailInput() {
const [email, setEmail] = useState('')
const [isValid, setIsValid] = useState(null)
const validateEmail = (value) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return regex.test(value)
}
const handleChange = (e) => {
const value = e.target.value
setEmail(value)
if (value.length > 0) {
setIsValid(validateEmail(value))
} else {
setIsValid(null)
}
}
return (
<div className="relative">
<input
type="email"
value={email}
onChange={handleChange}
className={`
w-full px-4 py-2 border-2 rounded-md transition-all duration-200
${isValid === true ? 'border-green-500' : ''}
${isValid === false ? 'border-red-500' : ''}
`}
/>
{isValid === true && (
<CheckIcon className="absolute right-3 top-3 text-green-500 animate-scale-in" />
)}
{isValid === false && (
<XIcon className="absolute right-3 top-3 text-red-500 animate-shake" />
)}
</div>
)
}Password Strength Indicator
export function PasswordInput() {
const [password, setPassword] = useState('')
const [strength, setStrength] = useState(0)
const calculateStrength = (pwd) => {
let score = 0
if (pwd.length >= 8) score += 25
if (pwd.length >= 12) score += 25
if (/[a-z]/.test(pwd) && /[A-Z]/.test(pwd)) score += 25
if (/\d/.test(pwd)) score += 15
if (/[^a-zA-Z0-9]/.test(pwd)) score += 10
return Math.min(score, 100)
}
const handleChange = (e) => {
const value = e.target.value
setPassword(value)
setStrength(calculateStrength(value))
}
const getStrengthColor = () => {
if (strength < 40) return 'bg-red-500'
if (strength < 70) return 'bg-yellow-500'
return 'bg-green-500'
}
const getStrengthLabel = () => {
if (strength < 40) return 'Weak'
if (strength < 70) return 'Medium'
return 'Strong'
}
return (
<div>
<input
type="password"
value={password}
onChange={handleChange}
className="w-full px-4 py-2 border rounded-md"
/>
{password && (
<div className="mt-2">
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className={`h-full transition-all duration-300 ${getStrengthColor()}`}
style={{ width: `${strength}%` }}
/>
</div>
<p className="text-sm mt-1">{getStrengthLabel()}</p>
</div>
)}
</div>
)
}---
3. Toggle & Switch Interactions
export function AnimatedToggle({ checked, onChange }) {
return (
<button
role="switch"
aria-checked={checked}
onClick={() => onChange(!checked)}
className={`
relative w-14 h-8 rounded-full transition-colors duration-300
${checked ? 'bg-blue-500' : 'bg-gray-300'}
`}
>
<span
className={`
absolute top-1 left-1 w-6 h-6 bg-white rounded-full
transition-transform duration-300 ease-in-out
${checked ? 'transform translate-x-6' : ''}
`}
/>
</button>
)
}---
4. Loading States
Skeleton Screens
export function SkeletonCard() {
return (
<div className="border rounded-lg p-4 space-y-3">
<div className="h-4 bg-gray-200 rounded animate-pulse w-3/4" />
<div className="h-4 bg-gray-200 rounded animate-pulse w-full" />
<div className="h-4 bg-gray-200 rounded animate-pulse w-5/6" />
<div className="h-8 bg-gray-200 rounded animate-pulse w-1/3 mt-4" />
</div>
)
}Progress Indicators
export function ProgressBar({ progress }) {
return (
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full transition-all duration-300 ease-out"
style={{ width: `${progress}%` }}
/>
</div>
)
}
// Circular progress
export function CircularProgress({ progress }) {
const radius = 40
const circumference = 2 * Math.PI * radius
const offset = circumference - (progress / 100) * circumference
return (
<svg width="100" height="100">
<circle
cx="50"
cy="50"
r={radius}
fill="none"
stroke="#e5e7eb"
strokeWidth="8"
/>
<circle
cx="50"
cy="50"
r={radius}
fill="none"
stroke="#3b82f6"
strokeWidth="8"
strokeDasharray={circumference}
strokeDashoffset={offset}
transform="rotate(-90 50 50)"
style={{ transition: 'stroke-dashoffset 0.3s ease' }}
/>
</svg>
)
}---
5. Feedback Notifications
Toast Notifications
import { useState, useEffect } from 'react'
export function Toast({ message, type = 'success', duration = 3000, onClose }) {
const [isVisible, setIsVisible] = useState(true)
useEffect(() => {
const timer = setTimeout(() => {
setIsVisible(false)
setTimeout(onClose, 300) // Wait for fade-out animation
}, duration)
return () => clearTimeout(timer)
}, [duration, onClose])
const bgColor = {
success: 'bg-green-500',
error: 'bg-red-500',
warning: 'bg-yellow-500',
info: 'bg-blue-500',
}[type]
return (
<div
className={`
fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg text-white
transition-all duration-300 ${bgColor}
${isVisible ? 'opacity-100 transform translate-y-0' : 'opacity-0 transform -translate-y-2'}
`}
>
{message}
</div>
)
}Snackbar with Action
export function Snackbar({ message, action, onAction, onClose }) {
return (
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-6 py-3 rounded-lg shadow-lg flex items-center gap-4 animate-slide-up">
<span>{message}</span>
{action && (
<button
onClick={onAction}
className="text-blue-400 hover:text-blue-300 font-medium"
>
{action}
</button>
)}
<button onClick={onClose} className="ml-2 hover:text-gray-300">
×
</button>
</div>
)
}---
6. Drag and Drop Interactions
import { useState } from 'react'
export function DraggableItem({ id, children }) {
const [isDragging, setIsDragging] = useState(false)
return (
<div
draggable
onDragStart={(e) => {
setIsDragging(true)
e.dataTransfer.effectAllowed = 'move'
e.dataTransfer.setData('text/html', e.target.parentNode.innerHTML)
}}
onDragEnd={() => setIsDragging(false)}
className={`
p-4 border rounded-lg cursor-move transition-all duration-200
${isDragging
? 'opacity-50 scale-95 rotate-2'
: 'opacity-100 scale-100 hover:shadow-md'
}
`}
>
{children}
</div>
)
}
export function DropZone({ onDrop, children }) {
const [isOver, setIsOver] = useState(false)
return (
<div
onDragOver={(e) => {
e.preventDefault()
setIsOver(true)
}}
onDragLeave={() => setIsOver(false)}
onDrop={(e) => {
e.preventDefault()
setIsOver(false)
onDrop(e)
}}
className={`
min-h-[200px] border-2 border-dashed rounded-lg p-4
transition-all duration-200
${isOver ? 'border-blue-500 bg-blue-50' : 'border-gray-300'}
`}
>
{children}
</div>
)
}---
7. Like/Favorite Animations
export function LikeButton() {
const [isLiked, setIsLiked] = useState(false)
return (
<button
onClick={() => setIsLiked(!isLiked)}
className="relative p-2 rounded-full hover:bg-gray-100 transition-colors"
>
<svg
className={`
w-6 h-6 transition-all duration-300
${isLiked ? 'scale-110 fill-red-500 stroke-red-500' : 'fill-none stroke-gray-400'}
`}
viewBox="0 0 24 24"
strokeWidth="2"
>
<path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z" />
</svg>
{isLiked && (
<span className="absolute inset-0 rounded-full bg-red-500 opacity-0 animate-ping-once" />
)}
</button>
)
}---
Animation Easing Curves
/* Timing functions for different effects */
/* Default (smooth acceleration/deceleration) */
transition: all 0.3s ease;
/* Start slow, accelerate (entering elements) */
transition: all 0.3s ease-out;
/* Start fast, decelerate (exiting elements) */
transition: all 0.3s ease-in;
/* Sharp acceleration (material design) */
transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
/* Bounce effect */
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
/* Spring effect */
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);---
Accessibility Considerations
1. Respect User Preferences
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}2. Provide Alternative Feedback
Never rely solely on motion—provide additional visual or text feedback for users with reduced motion preferences.
3. Keyboard Interaction
Ensure all micro-interactions triggered by mouse also work with keyboard (focus states, Enter/Space keys).
// Good: Works with both mouse and keyboard
<button
onClick={handleClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleClick()
}
}}
className="hover:bg-blue-600 focus:ring-2 focus:ring-blue-500"
>
Click me
</button>---
Common Mistakes to Avoid
AVOID: Over-animation (too many simultaneous animations distract users) AVOID: Slow animations (>600ms feels sluggish) AVOID: Meaningless animation (decorative effects without functional purpose) AVOID: Missing feedback (no response to user actions) AVOID: Inconsistent timing (different durations for similar interactions) AVOID: Ignoring reduced motion (forcing animations on users with motion sensitivities)
---
Testing Micro-interactions
Checklist:
- [ ] Animations feel smooth at 60fps
- [ ] Timing is appropriate (200-400ms for most transitions)
- [ ] Feedback is instant (<100ms for critical interactions)
- [ ] Works on low-end devices without janking
- [ ] Respects prefers-reduced-motion
- [ ] All states are visually distinct (hover, active, focus, disabled)
- [ ] Keyboard users get equivalent feedback
- [ ] Animations don't block interaction (no forced waiting)
---
Resources
- Framer Motion: framer.com/motion (React animation library)
- GSAP: greensock.com/gsap (Professional animation platform)
- Lottie: airbnb.design/lottie (JSON-based animations)
- Interaction Design Foundation: interaction-design.org (Micro-interactions UX)
- Material Design 3 Motion: m3.material.io/styles/motion (M3 easing, duration, transitions)
---
Related Templates
template-state-machines.md— Managing complex interaction statestemplate-animation-library.md— Using Framer Motion and GSAP- template-wcag-testing.md — Accessibility testing (includes reduced motion)
UI Specification Template
Complete specification for a page/screen design handoff.
---
Page Information
| Field | Value |
|---|---|
| Page Name | [e.g., Product Detail Page] |
| URL/Route | [e.g., /products/{id}] |
| Platform | [Web/iOS/Android] |
| Version | [e.g., 1.0] |
| Last Updated | [Date] |
| Designer | [Name] |
---
Page Purpose
Primary Goal: [What is the main purpose of this page?]
Success Metrics:
- [Metric 1: e.g., Add to cart rate > 10%]
- [Metric 2: e.g., Time on page < 2 minutes]
User Entry Points:
- [ ] [Entry point 1: e.g., Search results]
- [ ] [Entry point 2: e.g., Category page]
- [ ] [Entry point 3: e.g., Direct link]
---
Layout Structure
Breakpoints
| Breakpoint | Min Width | Columns | Behavior |
|---|---|---|---|
| Mobile | 0 | 4 | [Stack/scroll behavior] |
| Tablet | 768px | 8 | [Layout changes] |
| Desktop | 1024px | 12 | [Full layout] |
| Wide | 1440px | 12 | [Max width/centering] |
Content Sections
┌─────────────────────────────────────────┐
│ HEADER (Component: GlobalHeader) │
├─────────────────────────────────────────┤
│ HERO SECTION │
│ [Component: ProductGallery] │
│ [Component: ProductInfo] │
├─────────────────────────────────────────┤
│ DETAILS SECTION │
│ [Component: TabsContainer] │
│ - Description │
│ - Specifications │
│ - Reviews │
├─────────────────────────────────────────┤
│ RELATED PRODUCTS │
│ [Component: ProductCarousel] │
├─────────────────────────────────────────┤
│ FOOTER (Component: GlobalFooter) │
└─────────────────────────────────────────┘---
Component Specifications
Component: [Name]
Purpose: [What this component does]
Variants:
| Variant | Use Case |
|---|---|
| default | [Standard use] |
| compact | [Mobile/constrained] |
| expanded | [Detail view] |
Props/Data:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| [prop1] | string | Yes | - | [Description] |
| [prop2] | number | No | 0 | [Description] |
| [prop3] | boolean | No | false | [Description] |
States:
| State | Visual Change | Trigger |
|---|---|---|
| Default | [Base appearance] | - |
| Hover | [Highlight/shadow] | Mouse hover |
| Focus | [Focus ring] | Keyboard focus |
| Active | [Pressed effect] | Click/tap |
| Loading | [Skeleton/spinner] | Data fetching |
| Disabled | [50% opacity] | isDisabled prop |
| Error | [Red border/text] | Validation failure |
Responsive Behavior:
| Breakpoint | Behavior |
|---|---|
| Mobile | [e.g., Stack vertically, hide secondary actions] |
| Tablet | [e.g., 2-column grid] |
| Desktop | [e.g., Full horizontal layout] |
Accessibility:
- Role: [e.g., button, link, region]
- ARIA: [Required aria attributes]
- Keyboard: [Tab order, shortcuts]
- Focus: [Focus management behavior]
Code Example:
<ComponentName
prop1="value"
prop2={123}
prop3={false}
onAction={handleAction}
/>---
Interaction Specifications
Interaction: [Name]
Trigger: [What initiates this interaction]
Behavior: 1. [Step 1] 2. [Step 2] 3. [Step 3]
Animation:
| Property | Value | Duration | Easing |
|---|---|---|---|
| opacity | 0 → 1 | 200ms | ease-out |
| transform | translateY(10px) → 0 | 200ms | ease-out |
Error Handling:
| Error Case | Behavior |
|---|---|
| [Error 1] | [Response] |
| [Error 2] | [Response] |
---
Content Requirements
Static Content
| Element | Content | Character Limit | Notes |
|---|---|---|---|
| Page Title | [Title] | 60 chars | SEO title |
| H1 | [Heading] | 70 chars | - |
| CTA Button | [Label] | 25 chars | - |
Dynamic Content
| Data Point | Source | Fallback |
|---|---|---|
| [Field 1] | [API endpoint/field] | [Default value] |
| [Field 2] | [API endpoint/field] | [Default value] |
Images
| Image | Dimensions | Format | Alt Text Pattern |
|---|---|---|---|
| Hero | 1200×600 | WebP + fallback | [Description pattern] |
| Thumbnail | 300×300 | WebP + fallback | [Description pattern] |
---
State Handling
Empty State
When: [Condition for empty state]
Display:
- Illustration: [Yes/No, description]
- Heading: [Copy]
- Description: [Copy]
- CTA: [Label] → [Action]
Loading State
When: [Initial load, data refresh, etc.]
Display:
- Type: [Skeleton/Spinner/Progress]
- Duration threshold: [When to show]
- Transition: [How it appears/disappears]
Error State
When: [API failure, validation error, etc.]
| Error Type | Title | Message | Action |
|---|---|---|---|
| Network | [Title] | [Message] | [Retry/Contact] |
| Validation | [Title] | [Message] | [Fix guidance] |
| Permission | [Title] | [Message] | [Login/Request] |
---
Accessibility Checklist
WCAG 2.2 AA Compliance
Perceivable:
- [ ] Color contrast ≥ 4.5:1 (text), ≥ 3:1 (large text)
- [ ] Text resizable to 200%
- [ ] Non-text content has alt text
- [ ] No information conveyed by color alone
Operable:
- [ ] All interactive elements keyboard accessible
- [ ] Focus visible and logical order
- [ ] No keyboard traps
- [ ] Skip links available
- [ ] Touch targets ≥ 24×24px (44×44px recommended)
Understandable:
- [ ] Labels describe purpose
- [ ] Error messages specific and helpful
- [ ] Consistent navigation
- [ ] Language specified
Robust:
- [ ] Valid HTML
- [ ] ARIA used correctly
- [ ] Compatible with assistive technology
Screen Reader Notes
| Element | Announcement | Notes |
|---|---|---|
| [Element 1] | [What is announced] | [Special handling] |
| [Element 2] | [What is announced] | [Special handling] |
---
Design Tokens Used
Colors
| Token | Value | Usage |
|---|---|---|
| --color-primary | #2196F3 | CTAs, links |
| --color-text-primary | #1A1A1A | Body text |
| --color-background | #FFFFFF | Page background |
Typography
| Token | Value | Usage |
|---|---|---|
| --font-size-h1 | 2.25rem | Page heading |
| --font-size-body | 1rem | Body text |
| --font-weight-bold | 700 | Headings, emphasis |
Spacing
| Token | Value | Usage |
|---|---|---|
| --space-4 | 1rem | Standard padding |
| --space-8 | 2rem | Section spacing |
---
Assets Required
Icons
| Icon | Source | Size | Notes |
|---|---|---|---|
| [Icon name] | [Lucide/custom] | 24px | [Usage] |
Images
| Asset | Location | Formats |
|---|---|---|
| [Asset name] | /assets/images/ | WebP, PNG fallback |
---
Developer Notes
Dependencies
- [Component library: e.g., shadcn/ui]
- [Animation library: e.g., Framer Motion]
- [Form library: e.g., React Hook Form]
API Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| [/api/...] | GET | [Data fetch] |
| [/api/...] | POST | [Action] |
Performance Requirements
| Metric | Target |
|---|---|
| LCP | < 2.5s |
| INP | < 200ms |
| CLS | < 0.1 |
Known Issues/Constraints
- [Issue 1]
- [Issue 2]
---
Approval
| Role | Name | Date | Status |
|---|---|---|---|
| Designer | [Name] | [Date] | ✓ Approved |
| Developer | [Name] | [Date] | ☐ Pending |
| Product | [Name] | [Date] | ☐ Pending |
UI/UX Design Review Checklist
Comprehensive checklist for reviewing UI/UX design quality across all platforms.
---
Core Review (All Products)
1. Interaction Design Fundamentals
| Check | Pass | Notes |
|---|---|---|
| Affordances visible: Interactive elements look clickable/tappable | [ ] | |
| Feedback immediate: Every action produces visible response (aim ~0.1s for direct manipulation) | [ ] | |
| System status visible: User always knows what's happening | [ ] | |
| Error prevention: Constraints prevent invalid states | [ ] | |
| Error recovery: Clear error messages with fix instructions | [ ] | |
| Undo available: Destructive actions are reversible or confirmed | [ ] | |
| Consistency: Same action = same result throughout | [ ] |
2. Information Architecture
| Check | Pass | Notes |
|---|---|---|
| Primary action obvious: Clear on first scan [Inference] | [ ] | |
| Navigation supports scale: Search/filter/saved views for large content | [ ] | |
| Breadcrumbs/back navigation: User can always return | [ ] | |
| Labels plain language: No jargon, domain terms explained | [ ] | |
| Search/filter available: For content-heavy views | [ ] | |
| Empty states actionable: Guide user to populate | [ ] |
3. State Handling
| Check | Pass | Notes |
|---|---|---|
| Loading states: Placeholder/progress for noticeable waits | [ ] | |
| Empty states: Illustration + message + CTA | [ ] | |
| Error states: Specific message + recovery action | [ ] | |
| Partial data: Graceful handling of incomplete data | [ ] | |
| Offline state: Clear indication + cached content | [ ] | |
| Success confirmation: Toast/inline for completed actions | [ ] |
4. Form Design
| Check | Pass | Notes |
|---|---|---|
| Labels visible: Every input has associated label | [ ] | |
| Validation on blur: Not on keystroke, not only on submit | [ ] | |
| Error messages inline: Next to the problem field | [ ] | |
| Required fields marked: Asterisk or explicit text | [ ] | |
| Input types correct: Email, tel, number where appropriate | [ ] | |
| Autocomplete attributes: For user data fields | [ ] | |
| Progress indicator: For multi-step forms | [ ] | |
| No redundant entry: Avoid re-entering known info (WCAG 3.3.7) | [ ] | |
| Accessible auth: Avoid cognitive tests-only (WCAG 3.3.8) | [ ] |
5. Accessibility (WCAG 2.2 AA)
| Check | Pass | Notes |
|---|---|---|
| Color contrast ≥4.5:1: Text on background | [ ] | |
| Focus visible: Clear focus indicator (WCAG 2.4.7/2.4.13) | [ ] | |
| Focus not obscured: Sticky UI doesn’t hide focus (WCAG 2.4.11) | [ ] | |
| Focus order logical: Matches visual reading order | [ ] | |
| Keyboard navigable: All features work without mouse | [ ] | |
| Target size: Meets WCAG 2.5.8 minimum (24×24 CSS px) | [ ] | |
| Screen reader compatible: ARIA labels, semantic HTML | [ ] | |
Reduced motion: Respects prefers-reduced-motion | [ ] | |
| Skip links: "Skip to main content" present | [ ] | |
| Dragging alternatives: Non-drag option exists (WCAG 2.5.7) | [ ] | |
| Consistent help: Help is placed consistently (WCAG 3.2.6) | [ ] |
6. Performance UX
| Check | Pass | Notes |
|---|---|---|
| LCP <2.5s: Largest content loads quickly | [ ] | |
| Responsive to input: INP ≤200ms | [ ] | |
| No layout shift: CLS ≤0.1 | [ ] | |
| Images optimized: Responsive, lazy-loaded, compressed | [ ] | |
| Skeleton matches content: Loading placeholders reflect final layout | [ ] | |
| Optimistic UI: For high-confidence actions | [ ] |
7. Platform Compliance
Web
| Check | Pass | Notes |
|---|---|---|
Semantic HTML: <button>, <nav>, <main> used correctly | [ ] | |
| 320px viewport: No horizontal scroll at mobile width | [ ] | |
| Browser back/forward: History state preserved | [ ] |
iOS
| Check | Pass | Notes |
|---|---|---|
| Safe areas respected: Notch/Dynamic Island handled | [ ] | |
| Dynamic Type supported: Text scales with system setting | [ ] | |
| Native patterns: Tab bar, navigation bar as expected | [ ] |
Android
| Check | Pass | Notes |
|---|---|---|
| Material 3 patterns: Consistent with platform | [ ] | |
| Back gesture handled: Predictive back supported | [ ] | |
| Edge-to-edge layout: System bars integrated | [ ] |
8. Design System Alignment
| Check | Pass | Notes |
|---|---|---|
| Tokens used: Colors, spacing from design system | [ ] | |
| Components reused: No one-off implementations | [ ] | |
| Variants correct: Appropriate component variant selected | [ ] | |
| No custom styling: Overrides documented if necessary | [ ] |
---
Optional: AI/Automation Features
Complete this section ONLY if the product includes AI/ML features.
AI Transparency
| Check | Pass | Notes |
|---|---|---|
| AI involvement disclosed: User knows AI is involved | [ ] | |
| Processing status shown: AI thinking/generating visible | [ ] | |
| Source attribution: AI-generated content cited | [ ] | |
| Confidence indicated: Uncertainty communicated visually | [ ] | |
| Limitations disclosed: Known limitations documented | [ ] |
AI User Control
| Check | Pass | Notes |
|---|---|---|
| Stop/cancel available: User can interrupt AI | [ ] | |
| Regenerate option: User can request alternative | [ ] | |
| Edit capability: User can modify AI output | [ ] | |
| Override possible: Human decision > AI suggestion | [ ] | |
| Disable option: User can turn off AI features | [ ] | |
| Feedback mechanism: User can rate AI quality | [ ] |
AI Error Handling
| Check | Pass | Notes |
|---|---|---|
| Failure graceful: AI failure doesn't block workflow | [ ] | |
| Fallback available: Manual alternative exists | [ ] | |
| Error explanation: Why AI failed is communicated | [ ] | |
| Retry easy: User can easily retry failed AI operation | [ ] |
---
Review Summary
Date: _______________ Reviewer: _______________ Product/Feature: _______________
Findings Summary
| Category | Pass | Fail | N/A | Priority Issues |
|---|---|---|---|---|
| Interaction Design | /7 | |||
| Information Architecture | /6 | |||
| State Handling | /6 | |||
| Form Design | /7 | |||
| Accessibility | /8 | |||
| Performance UX | /5 | |||
| Platform Compliance | /3 | |||
| Design System | /4 | |||
| AI/Automation (if applicable) | /15 |
Top 3 Issues to Address
1. [Issue]: [Description] — Severity: Critical/Major/Minor 2. [Issue]: [Description] — Severity: Critical/Major/Minor 3. [Issue]: [Description] — Severity: Critical/Major/Minor
Recommendations
[Prioritized action items]
---
References (Primary Sources)
- WCAG 2.2 (W3C Recommendation, 12 Dec 2024): https://www.w3.org/TR/WCAG22/
- WAI-ARIA Authoring Practices Guide: https://www.w3.org/WAI/ARIA/apg/
- Core Web Vitals (LCP/INP/CLS thresholds): https://web.dev/vitals/
- Response time thresholds (0.1s / 1s / 10s): https://www.nngroup.com/articles/response-times-3-important-limits/
prefers-reduced-motion(Media Queries Level 5): https://www.w3.org/TR/mediaqueries-5/#prefers-reduced-motion
Prototype-to-Production Alignment
Operational checklist for closing the gap between design prototypes and shipped UI. Use when a team says "prototype is close, but the real page feels off."
Last Updated: March 2026
---
Dashboard Lesson Pack
1) Desktop choreography before pixel tweaks
- Start with asymmetric columns (about 60/40), not strict mirrored rows.
- Keep independent vertical rhythm per column; align only intentional pairs.
- Prevent dead-right canvas by moving right-column cards up when left grows.
2) Compactness heuristics (web)
- Tighten module spacing to ~10-14px in related zones.
- Remove decorative section labels unless they add navigation value.
- Merge adjacent related blocks into one container if this reduces visual fragmentation.
- Default to flat/compact, not expandable/accordion: Use static inline content unless the user explicitly needs progressive disclosure. Expanding panels add interaction cost, layout shift, and complexity. Only use accordions/drawers when content volume genuinely exceeds viewport tolerance.
3) Control standardization
- One primary action per card context.
- One share style across dashboard; avoid duplicate share controls in one context.
- Replace oversized full-width disclosure controls with compact secondary pills/links.
- Keep day chips + "This Week" as one visual family with stable alignment.
4) De-duplication rules
- Do not repeat the same meaning in metadata, chips, and body hints.
- If context is shown under the hero title (date/moon/phase), remove duplicate decorative restatements.
- Remove duplicate guidance fragments across Signal/Tension and Do/Do not.
5) Banner governance
- Promotional/validation banners are contextual, not always-on.
- Dismissal must persist; route-level suppression is required for noisy cards.
- Passive banners should not look selected (no heavy "active" border unless stateful).
6) Loading-state quality bar
- Skeletons must map to final IA (shape/count/relative sizing).
- Keep header context visible while loading.
- Avoid disconnected full-page placeholder compositions that create "messy" perception.
7) i18n requirement in design handoff
- Every user-visible string (including aria labels/tooltips) must be key-based.
- Define EN keys as baseline in spec before implementation.
- Reject hardcoded strings in final QA.
8) Final QA pass sequence
1. Desktop rhythm and right-side void check (1440+). 2. Mobile chip size/contrast/alignment check. 3. Control consistency sweep (buttons, links, share, chips). 4. Expansion behavior check (no whitespace blowouts). 5. Loading state and feature-surfacing sanity check.
---
Ops UI QA: Design-to-Ship Checks
Use this after implementing visual changes to prevent "looks good in Figma, broken in prod" outcomes.
Fast QA Commands
# 1) Run build first (layout and import safety)
npm run build
# 2) Run accessibility smoke if available
npm run test:e2e -- --grep "@a11y"
# 3) Capture route screenshots for review set
# (replace with your project screenshot task)
npm run test:e2e -- --grep "@visual"Required Review Grid
Validate each critical screen for:
- desktop rhythm (1366+, 1440+, 1920),
- mobile spacing and tap targets (360-430 width),
- loading/empty/error state consistency,
- contrast and focus visibility,
- localization expansion (long strings, RTL if supported).
Operational Rule
Do not sign off on visual changes without state coverage (loading, empty, error, success). Most production regressions hide outside the default happy state.
Related skills
How it compares
Use software-ui-ux-design for web UX specs and guidance; choose Tailwind or component skills when the design is set and you only need CSS implementation patterns.
FAQ
What does software-ui-ux-design produce?
software-ui-ux-design generates consistent UI/UX designs and implementation guidance for web applications and interfaces. Output targets developers who need design direction before or during frontend build work.
How popular is software-ui-ux-design on skills.sh?
software-ui-ux-design from vasilyu1983/ai-agents-public shows 505 installs on skills.sh, indicating community adoption for web UI/UX design and implementation guidance workflows.