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