frontend-ui-engineering

frontend-ui-engineering

熱門

建立符合生產標準、無障礙且響應式的使用者介面。在建立或修改介面與頁面、建立元件、實作版面配置、滿足 WCAG 無障礙需求、管理狀態,或當輸出需要看起來像生產級品質而非 AI 生成時使用。

7.7萬星標
0分支
更新於 2026/7/3
SKILL.md
readonlyread-only
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)     → 應用程式範圍的複雜客戶端狀態

避免 props 鑽孔超過 3 層。 如果你正在將 props 傳遞給未使用它們的元件,請引入 context 或重構元件樹。

設計系統遵循

避免 AI 美學

AI 生成的 UI 有可識別的模式。避免所有這些:

AI 預設 問題所在 生產級品質
到處都是紫色/靛藍色 模型預設使用視覺上「安全」的色板,導致每個應用程式看起來都一樣 使用專案實際的色板
過多的漸層 漸層增加視覺雜訊,並與大多數設計系統衝突 平面或符合設計系統的微妙漸層
到處都是圓角 (rounded-2xl) 最大圓角表示「友善」,但忽略了真實設計中圓角半徑的層級 來自設計系統的一致 border-radius
通用的英雄區塊 模板驅動的佈局,與實際內容或使用者需求無關 內容優先的佈局
Lorem ipsum 風格的文字 佔位文字隱藏了真實內容會揭示的佈局問題(長度、換行、溢出) 逼真的佔位內容
到處都是過大的 padding 均勻的寬鬆 padding 破壞了視覺層級並浪費螢幕空間 一致的間距尺度
標準卡片網格 均勻網格是一種佈局捷徑,忽略了資訊優先級和掃描模式 目的驅動的佈局
大量陰影的設計 多層陰影增加了與內容競爭的深度,並在低階裝置上減慢渲染 除非設計系統指定,否則使用微妙或無陰影

間距與佈局

使用一致的間距尺度。不要發明數值:

/* 使用尺度:0.25rem 增量(或專案使用的任何尺度) */
/* 好 */  padding: 1rem;      /* 16px */
/* 好 */  gap: 0.75rem;       /* 12px */
/* 壞 */   padding: 13px;      /* 不在任何尺度上 */
/* 壞 */   margin-top: 2.3rem; /* 不在任何尺度上 */

字體排版

尊重字體層級:

h1 → 頁面標題(每頁一個)
h2 → 章節標題
h3 → 子章節標題
body → 預設文字
small → 次要/輔助文字

不要跳過標題層級。不要對非標題內容使用標題樣式。

色彩

  • 使用語意色彩 token: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。

載入與轉場

// 骨架載入(內容不要用 spinner)
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 中無無障礙警告