📘 Khy-OS 文档站 🧭 新手先读：核心概念

# Frontend UI Engineering

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

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

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

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

Design for mobile first, then expand:

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