SuperAPI AI Directory

frontend-ui-engineering

Frontend & websites

View repository

Updated 10-16 10:04

About this skill

Frontend UI Engineering

Overview

Build accessible, high-performance, visually polished production-grade user interfaces. The goal is to make the UI look like it was built by a design-conscious engineer at a top-tier company, not AI-generated. This means genuinely following the design system, correct accessibility, thoughtful interaction patterns, and avoiding that 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 component-related together:

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:

// 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 in responsibility:

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

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

Do not let props pass through more than 3 levels. If you are passing props layer by layer between components that do not use them, introduce context or refactor the component tree.

Follow the Design System

Avoid the AI Aesthetic

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

AI Default Why It Is a Problem Production-Grade Standard
Full-screen purple/indigo Models default to visually "safe" color schemes, making every app look the same Use the project's actual color palette
Overuse of gradients Gradients add visual noise and conflict with most design systems Flat or restrained gradients that match the design system
Everything rounded (rounded-2xl) Maximum rounding conveys "friendliness" but ignores the hierarchy of corner radii in real design Follow consistent border-radius in the design system
Cookie-cutter hero sections Template-driven layouts with no connection to actual content and user needs Content-first layouts
Lorem ipsum-style copy Placeholder text hides layout issues that only real content would expose (length, line breaks, overflow) Realistic placeholder content
Excessive padding everywhere Uniformly loose padding destroys visual hierarchy and wastes screen space Consistent spacing scale
Stock card grids Homogeneous grids are a layout shortcut that ignore information priority and scanning patterns Goal-driven layouts
Heavy shadow design Layer upon layer of shadows adds depth that competes with content for attention and slows rendering on low-end devices Use restrained or no shadows unless the design system explicitly specifies otherwise

Spacing and Layout

Use a consistent spacing scale; do not invent values out of thin air:

/* 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 heading hierarchy:

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

Do not skip heading levels. Do not 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 body text, 3:1 for large text)
  • Do not convey information by color alone (also use icons, text, or patterns)

Accessibility (WCAG 2.1 AA)

Every component must meet these standards:

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
<div role="button" tabIndex={0} onClick={handleClick}    // ✓ But prefer <button>
     onKeyDown={e => {
       if (e.key === 'Enter') handleClick();
       if (e.key === ' ') e.preventDefault();
     }}
     onKeyUp={e => {
       if (e.key === ' ') handleClick();
     }}>
  Click me
</div>

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

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

Focus management

// 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 states and error states

// 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

Mobile-first design, then scale up to larger screens:

// 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

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

See also

For detailed accessibility requirements and testing tools, see ../../references/accessibility-checklist.md。

Common rationalization excuses

Excuse Reality
"Accessibility is a nice-to-have" In many jurisdictions it is a legal requirement, and it is also an engineering quality standard.
"We'll make it responsive later" Retrofitting responsive design is 3 times harder than doing it right from the start.
"The design isn't finalized yet, so skip styling for now" Use the design system defaults. An unstyled UI gives reviewers a broken first impression.
"This is just a prototype" Prototypes become production code. Get the foundation right.
"The AI aesthetic is fine for now" It signals low quality. Use the project's real design system from the start.

Red flags

  • Components over 200 lines (split them)
  • Inline styles or arbitrary pixel values
  • Missing error states, loading states, or empty states
  • No keyboard navigation testing
  • Using color as the only indicator of state (red/green, with no text or icon)
  • A generic "AI look" (purple gradients, oversized cards, stock layouts)

Verification

After building the UI:

  • No console errors when components render
  • All interactive elements are keyboard accessible (navigate the page with the Tab key)
  • Screen readers can convey the page's content and structure
  • Responsive: works correctly at 320px, 768px, 1024px, 1440px
  • Loading, error, and empty states are all handled
  • Follows the project's design system (spacing, color, typography)
  • No accessibility warnings in dev tools or axe-core