mcpskills.net
SkillsMCPsAgentsPrompts
mcpskills.net — A curated directory of AI agent Skills and MCP servers
TermsPrivacy
← Back to Skills
Design

Frontend UI Engineering

Build production-quality UIs. Use when building or modifying user-facing interfaces, creating components, implementing layouts, or managing state.

by Addy OsmaniRepository →Source →

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