frontend-ui-engineering · git:20261012.4b2f1b0 · 2026-10-12 · sha256 1d68acbc85e6d69c

frontend-ui-engineering git:20261012.4b2f1b0A

Immutable. This exact content never changes and is served at /api/v1/blob/1d68acbc85e6d69c.

---
name: frontend-ui-engineering
description: Use this skill when building or modifying production-quality user-facing interfaces. Builds accessible, responsive, visually polished UIs with proper component architecture, design system adherence, and WCAG 2.1 AA compliance.
---

# Frontend UI Engineering

## Overview

Build production-quality user interfaces that are accessible, performant, and visually polished. The goal is UI that looks like it was built by a design-aware engineer at a top company — not like it was generated by an AI. This means real design system adherence, proper accessibility, thoughtful interaction patterns, and no generic "AI aesthetic."

## 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
    types.ts             # Component-specific types (if needed)
```

### Component Patterns

**Prefer composition over configuration:**

```tsx
// Good: Composable
<Tasks>
  <TaskItem task={task} onToggle={handleToggle} onDelete={handleDelete} />
</Tasks>

// Avoid: Over-configured
<SuperTaskPanel mode="edit" enableDrag={true} showCompleted={false} ... />
```

**Separate data fetching from presentation:**

```tsx
// Container: handles data
export function TaskListContainer() {
  const { tasks, isLoading, error } = useTasks();
  if (isLoading) return <LoadingSkeleton />;
  if (error) return <ErrorState message={error} />;
  if (tasks.length === 0) return <EmptyState />;
  return <TaskList tasks={tasks} />;
}

// Presentation: handles rendering
export function TaskList({ tasks }: { tasks: Task[] }) {
  return <div>{tasks.map(task => <TaskItem key={task.id} task={task} />)}</div>;
}
```

## State Management

Choose the simplest approach that works:

```
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)    → Remote data with caching
Global store (Zustand, Redux) → Complex client state shared app-wide
```

**Avoid prop drilling deeper than 3 levels.** If you're passing props through components that don't use them, introduce context or restructure the component tree.

## Design System Adherence

### Reference-led UI Quality

When the product needs a distinct visual direction:

1. Collect evidence before choosing a layout
2. Study 2-3 relevant screens. Record decisions about hierarchy, density, navigation, controls, responsive behavior, and interaction states
3. Turn those decisions into a short design contract before implementation
4. Rebuild using the product's own components, tokens, content, and visual language
5. Never copy another product's branding, proprietary text, or exact layout

### Avoid the AI Aesthetic

| AI Default | Why It Is a Problem | Production Quality |
|---|---|---|
| Purple/indigo color schemes | Models default to visually "safe" palettes | Use the project's actual color palette |
| Excessive gradients | Add visual noise, clash with design systems | Flat or subtle gradients from the design system |
| Maximum border-radius everywhere | Ignores the hierarchy of corner radii in real designs | Consistent border-radius from the design system |
| Generic hero sections | Template-driven, no connection to real content | Content-first layouts |
| Lorem ipsum-style copy | Hides layout problems that real content reveals | Realistic placeholder content |
| Oversized padding | Destroys visual hierarchy, wastes screen space | Consistent spacing scale |
| Stock card grids | Ignores information priority and scanning patterns | Purpose-driven layouts |
| Shadow-heavy design | Competes with content, slows rendering on low-end devices | Subtle or no shadows unless specified |

### Spacing and Layout

Use a consistent spacing scale. Don't invent values:

```css
/* Use the scale: 0.25rem increments (or whatever the project uses) */
/* Good */ padding: 1rem;  /* 16px */
/* Good */ gap: 0.75rem;   /* 12px */
/* Bad */  padding: 13px;  /* Not on any scale */
/* Bad */  margin-top: 2.3rem; /* Not on any scale */
```

### Typography

Respect the type hierarchy:

```
h1 → Page title (one per page)
h2 → Section title
h3 → Subsection title
body → Default text
small → Secondary/helper text
```

Don't skip heading levels. Don't use heading styles for non-heading content.

### Color

- Use semantic color tokens: `text-primary`, `bg-surface`, `border-default` — not raw hex values
- Ensure sufficient contrast (4.5:1 for normal text, 3:1 for large text)
- Don't rely solely on color to convey information

## Accessibility (WCAG 2.1 AA)

### Keyboard Navigation

Every interactive element must be keyboard accessible:

```tsx
// ✓ Focusable by default
<button onClick={handleClick}>Click me</button>

// ✓ Custom element with keyboard support
<div
  tabIndex={0}
  role="button"
  onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') handleClick(); }}
  onClick={handleClick}
>Click me</div>
```

### ARIA Labels

```tsx
// Label interactive elements that lack visible text
<button aria-label="Delete task" onClick={handleDelete}>
  <TrashIcon />
</button>

// Label form inputs
<label htmlFor="email">Email</label>
<input id="email" type="email" aria-describedby="email-hint" />
```

### Focus Management

```tsx
// Move focus when content changes (e.g., dialog opens)
function Dialog({ isOpen, onClose }: DialogProps) {
  useEffect(() => {
    if (isOpen) closeButtonRef.current?.focus();
  }, [isOpen]);
  // Trap focus inside dialog when open
}
```

### Meaningful Empty and Error States

```tsx
// Don't show blank screens
function TaskList({ tasks }: { tasks: Task[] }) {
  if (tasks.length === 0) {
    return (
      <div>
        <h3>No tasks yet</h3>
        <p>Get started by creating a new task.</p>
      </div>
    );
  }
  return <div>...</div>;
}
```

## Responsive Design

Design for mobile first, then expand. Test at these breakpoints: 320px, 768px, 1024px, 1440px.

```tsx
// Mobile-first responsive using Tailwind
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
```

## Loading and Transitions

```tsx
// Skeleton loading (not spinners for content areas)
function TaskListSkeleton() {
  return (
    <div>
      {Array.from({ length: 3 }).map((_, i) => (
        <div key={i} className="h-16 skeleton-pulse rounded" />
      ))}
    </div>
  );
}

// Optimistic updates for perceived speed
function useToggleTask() {
  // Update UI immediately, rollback on error
}
```

## Common Rationalizations

| Rationalization | Reality |
|---|---|
| "Accessibility is a nice-to-have" | It's a legal requirement in many jurisdictions and an engineering quality standard. |
| "We'll make it responsive later" | Retrofitting responsive design is 3x harder than building it from the start. |
| "The design isn't final, so I'll skip styling" | Use the design system defaults. Unstyled UI creates a broken first impression. |
| "This is just a prototype" | Prototypes become production code. Build the foundation right. |
| "The AI aesthetic is fine for now" | It signals low quality. Use the project's actual design system from the start. |

## Red Flags

- Components with more than 200 lines (split them)
- Inline styles or arbitrary pixel values
- Missing error states, loading states, or empty states
- No keyboard navigation testing
- Color as the sole indicator of state (red/green without text or icons)
- Generic "AI look" (purple gradients, oversized cards, stock layouts)

## Verification

After building UI:

- [ ] Component renders without console errors
- [ ] All interactive elements are keyboard accessible (Tab through the page)
- [ ] Screen reader can convey the page's content and structure
- [ ] Responsive: works at 320px, 768px, 1024px, 1440px
- [ ] Loading, empty, error, success, and permission states handled when applicable
- [ ] Follows the project's design system (spacing, colors, typography)
- [ ] The rendered result passes a final UI-specific finish-gate review
- [ ] No accessibility warnings in dev tools or axe-core