Design
Frontend UI Engineering
Build production-quality UIs. Use when building or modifying user-facing interfaces, creating components, implementing layouts, or managing state.
Build production-quality user interfaces that are accessible, performant, and visually polished.
When to Use
- Building new UI components or pages
- Modifying existing user-facing interfaces
- Implementing responsive layouts
- Adding interactivity or state management
- Fixing visual or UX issues
Component Architecture
File Structure
Colocate everything related to a component:
src/components/
TaskList/
TaskList.tsx # Component implementation
TaskList.test.tsx # Tests
TaskList.stories.tsx # Storybook stories
use-task-list.ts # Custom hook
types.ts # Component-specific types
Component Patterns
Prefer composition over configuration:
// Good: Composable
<Card>
<CardHeader>
<CardTitle>Tasks</CardTitle>
</CardHeader>
<CardBody>
<TaskList tasks={tasks} />
</CardBody>
</Card>
// Avoid: Over-configured
<Card
title="Tasks"
headerVariant="large"
bodyPadding="md"
content={<TaskList tasks={tasks} />}
/>
Keep components focused:
export function TaskItem({ task, onToggle, onDelete }: TaskItemProps) {
return (
<li className="flex items-center gap-3 p-3">
<Checkbox checked={task.done} onChange={() => onToggle(task.id)} />
<span className={task.done ? 'line-through text-muted' : ''}>{task.title}</span>
<Button variant="ghost" size="sm" onClick={() => onDelete(task.id)}>
<TrashIcon />
</Button>
</li>
);
}
State Management
Choose the simplest approach that works:
| State Type | Use Case |
|---|---|
| Local state (useState) | Component-specific UI state |
| Lifted state | Shared between 2-3 sibling components |
| Context | Theme, auth, locale (read-heavy, write-rare) |
| URL state (searchParams) | Filters, pagination, shareable UI state |
| Server state (React Query, SWR) | Remote data with caching |
| Global store (Zustand, Redux) | Complex client state shared app-wide |
Avoid prop drilling deeper than 3 levels.
Avoid the AI Aesthetic
| AI Default | Problem | Production Quality |
|---|---|---|
| Purple/indigo everything | Models default to visually "safe" palettes | Use the project's actual color palette |
| Excessive gradients | Adds visual noise | Flat or subtle gradients matching the design system |
| Rounded everything | Maximum rounding ignores hierarchy | Consistent border-radius from the design system |
| Generic hero sections | Template-driven layout | Content-first layouts |
| Lorem ipsum-style copy | Hides layout problems | Realistic placeholder content |
| Oversized padding everywhere | Destroys visual hierarchy | Consistent spacing scale |
Accessibility (WCAG 2.1 AA)
Keyboard Navigation
// Every interactive element must be keyboard accessible
<button onClick={handleClick}>Click me</button> // ✓ Focusable by default
<div onClick={handleClick}>Click me</div> // ✗ Not focusable
ARIA Labels
// Label interactive elements that lack visible text
<button aria-label="Close dialog"><XIcon /></button>
// Label form inputs
<label htmlFor="email">Email</label>
<input id="email" type="email" />
Focus Management
function Dialog({ isOpen, onClose }: DialogProps) {
const closeRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (isOpen) closeRef.current?.focus();
}, [isOpen]);
return (
<dialog open={isOpen}>
<button ref={closeRef} onClick={onClose}>Close</button>
</dialog>
);
}
Responsive Design
Design for mobile first, then expand:
<div className="
grid grid-cols-1 /* Mobile: single column */
sm:grid-cols-2 /* Small: 2 columns */
lg:grid-cols-3 /* Large: 3 columns */
gap-4
">
Test at: 320px, 768px, 1024px, 1440px.
Loading States
// Skeleton loading (not spinners for content)
function TaskListSkeleton() {
return (
<div className="space-y-3" aria-busy="true" aria-label="Loading tasks">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="h-12 bg-muted animate-pulse rounded" />
))}
</div>
);
}
Verification
- Component renders without console errors
- All interactive elements are keyboard accessible
- Screen reader can convey the page's content and structure
- Responsive: works at 320px, 768px, 1024px, 1440px
- Loading, error, and empty states all handled
- Follows the project's design system
- No accessibility warnings in dev tools or axe-core