frontend-ui-engineering

frontend-ui-engineering

热门

构建生产级、可访问、响应式的用户界面。在构建或修改界面和页面、创建组件、实现布局、满足WCAG无障碍要求、管理状态,或当输出需要看起来像生产级而非AI生成时使用。

7.7万Star
0Fork
更新于 2026/7/3
SKILL.md
readonly只读
name
frontend-ui-engineering
description

构建生产级、可访问、响应式的用户界面。在构建或修改界面和页面、创建组件、实现布局、满足WCAG无障碍要求、管理状态,或当输出需要看起来像生产级而非AI生成时使用。

前端UI工程

概述

构建生产级的用户界面,要求可访问、高性能且视觉精致。目标是让UI看起来像是由顶级公司中具有设计意识的工程师构建的——而不是由AI生成的。这意味着真正的设计系统遵循、适当的无障碍、深思熟虑的交互模式,以及没有通用的“AI美学”。

使用时机

  • 构建新的UI组件或页面
  • 修改现有的用户界面
  • 实现响应式布局
  • 添加交互性或状态管理
  • 修复视觉或UX问题

组件架构

文件结构

将与组件相关的所有内容放在一起:

src/components/
  TaskList/
    TaskList.tsx          # 组件实现
    TaskList.test.tsx     # 测试
    TaskList.stories.tsx  # Storybook故事(如果使用)
    use-task-list.ts      # 自定义Hook(如果状态复杂)
    types.ts              # 组件特定类型(如果需要)

组件模式

优先组合而非配置:

// 好:可组合
<Card>
  <CardHeader>
    <CardTitle>任务</CardTitle>
  </CardHeader>
  <CardBody>
    <TaskList tasks={tasks} />
  </CardBody>
</Card>

// 避免:过度配置
<Card
  title="任务"
  headerVariant="large"
  bodyPadding="md"
  content={<TaskList tasks={tasks} />}
/>

保持组件专注:

// 好:只做一件事
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>
  );
}

将数据获取与展示分离:

// 容器:处理数据
export function TaskListContainer() {
  const { tasks, isLoading, error } = useTasks();

  if (isLoading) return <TaskListSkeleton />;
  if (error) return <ErrorState message="加载任务失败" retry={refetch} />;
  if (tasks.length === 0) return <EmptyState message="暂无任务" />;

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

// 展示:处理渲染
export function TaskList({ tasks }: { tasks: Task[] }) {
  return (
    <ul role="list" className="divide-y">
      {tasks.map(task => <TaskItem key={task.id} task={task} />)}
    </ul>
  );
}

状态管理

选择最简单有效的方法:

本地状态 (useState)           → 组件特定的UI状态
提升状态                      → 在2-3个兄弟组件间共享
Context                       → 主题、认证、语言(读多写少)
URL状态 (searchParams)        → 筛选、分页、可共享的UI状态
服务端状态 (React Query, SWR) → 带缓存的远程数据
全局存储 (Zustand, Redux)     → 应用范围内共享的复杂客户端状态

避免超过3层的属性透传。 如果你通过不使用的组件传递属性,请引入context或重构组件树。

设计系统遵循

避免AI美学

AI生成的UI有可识别的模式。避免所有以下模式:

AI默认 问题 生产质量
到处都是紫色/靛蓝色 模型默认使用视觉上“安全”的调色板,使每个应用看起来都一样 使用项目实际的颜色调色板
过多的渐变 渐变增加视觉噪音,与大多数设计系统冲突 与设计系统匹配的平面或微妙渐变
所有元素都圆角 (rounded-2xl) 最大圆角表示“友好”,但忽略了真实设计中圆角半径的层次结构 来自设计系统的一致边框半径
通用的英雄区域 模板驱动的布局,与实际内容或用户需求无关 内容优先的布局
Lorem ipsum风格的占位文本 占位文本隐藏了真实内容暴露的布局问题(长度、换行、溢出) 真实的占位内容
到处是过大的内边距 均匀的大内边距破坏了视觉层次,浪费屏幕空间 一致的间距比例
通用的卡片网格 均匀网格是一种布局捷径,忽略了信息优先级和扫描模式 目的驱动的布局
阴影过重的设计 多层阴影增加了深度,与内容竞争,并减慢低端设备的渲染 除非设计系统指定,否则使用微妙或无阴影

间距和布局

使用一致的间距比例。不要发明值:

/* 使用比例:0.25rem增量(或项目使用的任何比例) */
/* 好 */  padding: 1rem;      /* 16px */
/* 好 */  gap: 0.75rem;       /* 12px */
/* 坏 */   padding: 13px;      /* 不在任何比例上 */
/* 坏 */   margin-top: 2.3rem; /* 不在任何比例上 */

排版

尊重标题层次:

h1 → 页面标题(每页一个)
h2 → 章节标题
h3 → 子章节标题
body → 默认文本
small → 次要/辅助文本

不要跳过标题级别。不要对非标题内容使用标题样式。

颜色

  • 使用语义化颜色标记:text-primarybg-surfaceborder-default——而不是原始十六进制值
  • 确保足够的对比度(普通文本4.5:1,大文本3:1)
  • 不要仅依赖颜色来传达信息(也要使用图标、文本或图案)

无障碍 (WCAG 2.1 AA)

每个组件必须满足以下标准:

键盘导航

// 每个交互元素必须可通过键盘访问
<button onClick={handleClick}>点击我</button>        // ✓ 默认可聚焦
<div onClick={handleClick}>点击我</div>               // ✗ 不可聚焦
<div role="button" tabIndex={0} onClick={handleClick}    // ✓ 但优先使用<button>
     onKeyDown={e => {
       if (e.key === 'Enter') handleClick();
       if (e.key === ' ') e.preventDefault();
     }}
     onKeyUp={e => {
       if (e.key === ' ') handleClick();
     }}>
  点击我
</div>

ARIA标签

// 为缺少可见文本的交互元素添加标签
<button aria-label="关闭对话框"><XIcon /></button>

// 为表单输入添加标签
<label htmlFor="email">邮箱</label>
<input id="email" type="email" />

// 或者当没有可见标签时使用aria-label
<input aria-label="搜索任务" type="search" />

焦点管理

// 内容变化时移动焦点
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}>关闭</button>
      {/* 对话框内容 */}
    </dialog>
  );
}

有意义的空状态和错误状态

// 不要显示空白屏幕
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">暂无任务</h3>
        <p className="mt-1 text-sm text-muted">开始创建一个新任务吧。</p>
        <Button className="mt-4" onClick={onCreateTask}>创建任务</Button>
      </div>
    );
  }

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

响应式设计

移动优先设计,然后扩展:

// Tailwind:移动优先响应式
<div className="
  grid grid-cols-1      /* 移动端:单列 */
  sm:grid-cols-2        /* 小屏:2列 */
  lg:grid-cols-3        /* 大屏:3列 */
  gap-4
">

在以下断点测试:320px、768px、1024px、1440px。

加载和过渡

// 骨架屏加载(内容区域不使用旋转器)
function TaskListSkeleton() {
  return (
    <div className="space-y-3" aria-busy="true" aria-label="加载任务中">
      {Array.from({ length: 3 }).map((_, i) => (
        <div key={i} className="h-12 bg-muted animate-pulse rounded" />
      ))}
    </div>
  );
}

// 乐观更新以提升感知速度
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);
    },
  });
}

参见

有关详细的无障碍要求和测试工具,请参阅 references/accessibility-checklist.md

常见理由

理由 现实
“无障碍是锦上添花” 在许多司法管辖区这是法律要求,也是工程质量标准。
“我们稍后再做响应式” 改造响应式设计比从一开始构建要难3倍。
“设计还没定稿,所以我先跳过样式” 使用设计系统默认值。未样式化的UI会给审查者留下糟糕的第一印象。
“这只是一个原型” 原型会变成生产代码。从一开始就打好基础。
“AI美学暂时可以接受” 它表明质量低下。从一开始就使用项目实际的设计系统。

警示信号

  • 组件超过200行(拆分它们)
  • 内联样式或任意像素值
  • 缺少错误状态、加载状态或空状态
  • 未测试键盘导航
  • 颜色作为状态的唯一指示符(红色/绿色没有文本或图标)
  • 通用的“AI外观”(紫色渐变、过大的卡片、模板布局)

验证

构建UI后:

  • [ ] 组件渲染无控制台错误
  • [ ] 所有交互元素可通过键盘访问(用Tab键浏览页面)
  • [ ] 屏幕阅读器能够传达页面的内容和结构
  • [ ] 响应式:在320px、768px、1024px、1440px下正常工作
  • [ ] 加载、错误和空状态均已处理
  • [ ] 遵循项目的设计系统(间距、颜色、排版)
  • [ ] 开发工具或axe-core中无无障碍警告