---
name: frontend-ui-engineering
description:
  Builds production-quality UIs. Use when building or modifying user-facing
  interfaces. Use when creating components, implementing layouts, managing
  state, or when the output needs to look and feel production-quality rather
  than AI-generated.
---

# Frontend UI Engineering

## Policy Capsule

- Build UI that meets WCAG 2.1 AA: every interactive element keyboard-accessible, ARIA labels on icon-only controls, focus moved when content changes, color never the sole indicator of state.
- Adhere to the project's design system — use semantic tokens (`text-primary`, `bg-surface`) and the spacing scale (no arbitrary pixel values, no inline styles).
- Avoid the "AI aesthetic": no default purple/indigo palettes, gratuitous gradients, blanket `rounded-2xl`, oversized padding, or shadow-heavy layering.
- Prefer composition over configuration; keep components focused (split anything past ~200 lines) and separate data-fetching containers from presentation components.
- Choose the simplest state mechanism that works: local `useState` → lifted state → context (read-heavy) → URL state → server-state library → global store; do not prop-drill past three levels.
- Render meaningful loading, empty, and error states for every async surface — skeletons for content (not spinners), and explicit empty-state copy + CTA.
- Design mobile-first and verify at 320 / 768 / 1024 / 1440 px breakpoints before claiming responsive.
- Respect the heading hierarchy (one `h1` per page, no skipped levels) and never re-purpose heading styles for non-heading content.
- Verify accessibility tooling (axe-core / dev-tools warnings) is clean and a screen reader can convey the page structure before marking the work done.

## 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:

```text
src/components/
  TaskList/
    TaskList.tsx          # Component implementation
    TaskList.test.tsx     # Tests
    TaskList.stories.tsx  # Storybook stories (if using)
    use-task-list.ts      # Custom hook (if complex state)
    types.ts              # Component-specific types (if needed)
```

### Component Patterns

**Prefer composition over configuration:**

```tsx
// 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:**

```tsx
// Good: Does one thing
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>
  );
}
```

**Separate data fetching from presentation:**

```tsx
// Container: handles data
export function TaskListContainer() {
  const { tasks, isLoading, error } = useTasks();

  if (isLoading) return <TaskListSkeleton />;
  if (error)
    return <ErrorState message="Failed to load tasks" retry={refetch} />;
  if (tasks.length === 0) return <EmptyState message="No tasks yet" />;

  return <TaskList tasks={tasks} />;
}

// Presentation: handles rendering
export function TaskList({ tasks }: { tasks: Task[] }) {
  return (
    <ul role="list" className="divide-y">
      {tasks.map((task) => (
        <TaskItem key={task.id} task={task} />
      ))}
    </ul>
  );
}
```

## State Management

**Choose the simplest approach that works:**

```text
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.** If you're passing props through
components that don't use them, introduce context or restructure the component
tree.

## Design System Adherence

### Avoid the AI Aesthetic

AI-generated UI has recognizable patterns. Avoid all of them:

| AI Default                       | Why It Is a Problem                                                                           | Production Quality                                      |
| -------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| Purple/indigo everything         | Models default to visually "safe" palettes, making every app look identical                   | Use the project's actual color palette                  |
| Excessive gradients              | Gradients add visual noise and clash with most design systems                                 | Flat or subtle gradients matching the design system     |
| Rounded everything (rounded-2xl) | Maximum rounding signals "friendly" but ignores the hierarchy of corner radii in real designs | Consistent border-radius from the design system         |
| Generic hero sections            | Template-driven layout with no connection to the actual content or user need                  | Content-first layouts                                   |
| Lorem ipsum-style copy           | Placeholder text hides layout problems that real content reveals (length, wrapping, overflow) | Realistic placeholder content                           |
| Oversized padding everywhere     | Equal generous padding destroys visual hierarchy and wastes screen space                      | Consistent spacing scale                                |
| Stock card grids                 | Uniform grids are a layout shortcut that ignores information priority and scanning patterns   | Purpose-driven layouts                                  |
| Shadow-heavy design              | Layered shadows add depth that competes with content and slows rendering on low-end devices   | Subtle or no shadows unless the design system specifies |

### 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:

```text
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 (use icons, text, or patterns
  too)

## Accessibility (WCAG 2.1 AA)

Every component must meet these standards:

### Keyboard Navigation

```tsx
// Every interactive element must be keyboard accessible
<button onClick={handleClick}>Click me</button>        // ✓ Focusable by default
<div onClick={handleClick}>Click me</div>               // ✗ Not focusable
<div role="button" tabIndex={0} onClick={handleClick}    // ✓ But prefer <button>
     onKeyDown={e => e.key === 'Enter' && handleClick()}>
  Click me
</div>
```

### ARIA Labels

```tsx
// 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" />

// Or use aria-label when no visible label exists
<input aria-label="Search tasks" type="search" />
```

### Focus Management

```tsx
// Move focus when content changes
function Dialog({ isOpen, onClose }: DialogProps) {
  const closeRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    if (isOpen) closeRef.current?.focus();
  }, [isOpen]);

  // Trap focus inside dialog when open
  return (
    <dialog open={isOpen}>
      <button ref={closeRef} onClick={onClose}>
        Close
      </button>
      {/* dialog content */}
    </dialog>
  );
}
```

### Meaningful Empty and Error States

```tsx
// Don't show blank screens
function TaskList({ tasks }: { tasks: Task[] }) {
  if (tasks.length === 0) {
    return (
      <div role="status" className="text-center py-12">
        <TasksEmptyIcon className="mx-auto h-12 w-12 text-muted" />
        <h3 className="mt-2 text-sm font-medium">No tasks</h3>
        <p className="mt-1 text-sm text-muted">
          Get started by creating a new task.
        </p>
        <Button className="mt-4" onClick={onCreateTask}>
          Create Task
        </Button>
      </div>
    );
  }

  return <ul role="list">...</ul>;
}
```

## Responsive Design

Design for mobile first, then expand:

```tsx
// Tailwind: mobile-first responsive
<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 these breakpoints: 320px, 768px, 1024px, 1440px.

## Loading and Transitions

```tsx
// 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>
  );
}

// Optimistic updates for perceived speed
function useToggleTask() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: toggleTask,
    onMutate: async (taskId) => {
      await queryClient.cancelQueries({ queryKey: ['tasks'] });
      const previous = queryClient.getQueryData(['tasks']);

      queryClient.setQueryData(['tasks'], (old: Task[]) =>
        old.map((t) => (t.id === taskId ? { ...t, done: !t.done } : t)),
      );

      return { previous };
    },
    onError: (_err, _taskId, context) => {
      queryClient.setQueryData(['tasks'], context?.previous);
    },
  });
}
```

## 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 for reviewers. |
| "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, error, and empty states all handled
- [ ] Follows the project's design system (spacing, colors, typography)
- [ ] No accessibility warnings in dev tools or axe-core
