agentsclimarketplace

Frontend ui engineering

Skill vinvcn/addyosmani-agent-skills-zh/skills/frontend-ui-engineering

本仓库是 addyosmani/agent-skills 的简体中文本地化版本。

Install
npx -y skills add vinvcn/addyosmani-agent-skills-zh --skill frontend-ui-engineering

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 23 stars23 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

构建生产级 UI。当构建或修改面向用户的界面时使用。创建组件、实现布局、管理状态,或输出需要看起来和感觉上都是生产级而不是 AI 生成时使用。

SKILL.md

9.9 KB, as published. Nobody here has run it

前端 UI 工程

概览

构建可访问、高性能、视觉打磨到位的生产级用户界面。目标是让 UI 看起来像由顶级公司的、有设计意识的工程师构建,而不是 AI 生成。这意味着真正遵循设计系统、正确的可访问性、周到的交互模式,以及没有通用的“AI aesthetic”。

何时使用

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

组件架构

文件结构

把与组件相关的一切放在一起:

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)

组件模式

优先组合,而不是配置:

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

保持组件聚焦:

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

把数据获取与展示分离:

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

状态管理

选择能工作的最简单方案:

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

避免超过 3 层的 prop drilling。 如果你把 props 传过不使用它们的组件,引入 context 或重构组件树。

遵循设计系统

避免 AI Aesthetic

AI 生成的 UI 有可识别的模式。全部避免:

AI 默认值为什么是问题生产质量
到处都是紫色/靛蓝模型默认选择视觉上“安全”的调色板,让每个应用看起来都一样使用项目实际的色彩方案
过度渐变渐变增加视觉噪声,并与多数设计系统冲突使用符合设计系统的平面色或细微渐变
所有东西都圆角(rounded-2xl)最大圆角传递“友好”感,但忽略真实设计中的圆角层级使用设计系统中一致的 border-radius
通用 hero sections模板化布局,与实际内容或用户需求无关内容优先的布局
Lorem ipsum 风格文案占位文本会隐藏真实内容暴露的布局问题(长度、换行、溢出)真实的占位内容
到处都是超大 padding一味宽松的等量 padding 会破坏视觉层级并浪费屏幕空间一致的间距尺度
Stock card grids统一网格是忽略信息优先级和扫描模式的布局捷径目标驱动的布局
阴影很重的设计层叠阴影增加与内容竞争的深度,并拖慢低端设备渲染除非设计系统指定,否则使用细微阴影或不使用阴影

间距与布局

使用一致的间距尺度。不要发明数值:

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

排版

尊重文字层级:

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

不要跳过标题级别。不要把标题样式用于非标题内容。

颜色

  • 使用语义化颜色 token:text-primarybg-surfaceborder-default,不要用原始 hex 值
  • 确保足够对比度(普通文本 4.5:1,大号文本 3:1)
  • 不要仅依赖颜色传达信息(也使用图标、文本或图案)

可访问性(WCAG 2.1 AA)

每个组件都必须满足这些标准:

键盘导航

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

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

焦点管理

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

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

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

响应式设计

先为移动端设计,再扩展:

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

在这些断点测试:320px、768px、1024px、1440px。

加载与过渡

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

另请参阅

关于详细可访问性要求和测试工具,见 references/accessibility-checklist.md

常见自我合理化

自我合理化现实
“Accessibility 只是锦上添花”在许多司法辖区它是法律要求,也是工程质量标准。
“我们之后再做 responsive”事后补响应式设计,比一开始就构建难 3 倍。
“设计还没最终定稿,所以我先跳过 styling”使用设计系统默认值。无样式 UI 会给 reviewer 留下破损的第一印象。
“这只是 prototype”原型会变成生产代码。把基础打对。
“AI aesthetic 现在也可以”它会传递低质量信号。从一开始就使用项目真实设计系统。

危险信号

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

验证

构建 UI 后:

  • 组件渲染时没有 console errors
  • 所有交互元素都可通过键盘访问(Tab through the page)
  • Screen reader 能传达页面内容和结构
  • 响应式:在 320px、768px、1024px、1440px 下工作
  • 加载、错误和空状态都已处理
  • 遵循项目设计系统(间距、颜色、排版)
  • dev tools 或 axe-core 中没有可访问性警告

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.