mcpField
W
web-ui-best-practices

web-ui-best-practices

github.com/jamditis设计与 UI438160 次安装4 天前更新
代码仓库

关于 web-ui-best-practices

面向开发者的 Web UI 设计技能,涵盖交互速度、无障碍、导航、状态持久化和现代 CSS,并提供实用审查原则。

主要功能

  1. 01快速交互反馈
  2. 02响应式 CSS 模式
  3. 03可恢复的用户状态
  4. 04导航与 URL 指引
  5. 05无障碍与输入指引

适用场景

  1. 01审查 Web 界面
  2. 02设计响应式组件
  3. 03改进表单与导航行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jamditis/claude-skills-journalism --skill web-ui-best-practices

安装后在 Claude Code 中输入/web-ui-best-practices确认已加载。

附带文件agents/openai.yaml

详细介绍

Web UI 最佳实践

构建快速、周到且尊重用户时间的 Web 界面的原则。这里的每条规则都是一项气味测试;有理由时,违反一条没关系,违反多条则说明 UI 需要改进。

速度

每次交互都应在 100ms 内完成。如果做不到,就营造出快速完成的假象。

  • 使用乐观 UI 更新,在服务器确认前显示结果
  • 对输入进行防抖,但绝不要让感知到的响应延迟
  • 在悬停或进入视口时,预取可能的下一条路由
  • 使用 will-change 和 transform 实现动画,绝不要使用 top/left
  • 用 performance.now() 测量,不要凭感觉判断
// Optimistic delete, remove from UI immediately, reconcile later
async function handleDelete(id) {
  setItems(prev => prev.filter(i => i.id !== id));
  try {
    await api.delete(`/items/${id}`);
  } catch {
    setItems(prev => [...prev, originalItem]);
    toast("Couldn't delete. Restored.");
  }
}
骨架屏加载状态

如果你知道即将加载内容的形状,就绝不要显示加载指示器。先渲染与布局匹配的骨架屏,再替换为真实内容。

.skeleton {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

现代 CSS 工具集

2023 至 2026 年间成熟的四项能力,改变了无需 JavaScript 构建组件级响应式布局和类 SPA 转场的方式。在添加框架之前,先考虑使用它们。

容器查询

容器查询让组件根据其容器的尺寸响应,而不是根据视口尺寸响应。同一张卡片可以在 300px 的侧边栏和 900px 的主列中呈现,无需在页面层面协调媒体查询。

.card-list {
  container-type: inline-size;
  container-name: cards;
}

@container cards (min-width: 480px) {
  .card { display: grid; grid-template-columns: 120px 1fr; }
}

自 2023 年起,已在所有主流浏览器中稳定支持。它取代了大多数“同一组件在两个地方需要呈现不同样式”的变通做法。

:has() 父选择器

:has() 让父元素可以根据其后代元素为自身设置样式,这是期待已久的“父选择器”。它适用于将表单字段标记为错误状态、将卡片标记为包含附加图片,或将行标记为包含已聚焦的输入框,而且无需 JS。

/* Highlight a form group when its input has focus */
.form-group:has(input:focus) {
  outline: 2px solid var(--color-primary);
}

/* Add bottom margin to articles that contain a figure */
article:has(figure) {
  margin-bottom: 2rem;
}

自 2023 年末起,已在 Chrome、Safari 和 Firefox 中稳定支持。它消除了一类由 JS 驱动的类切换操作。

视图转场

View Transitions API 可以在两个 DOM 状态之间(路由变更、模态框打开/关闭、列表项切换)实现动画,无需框架。浏览器会先截取旧状态的快照,替换为新状态,然后在两者之间交叉淡化或滑动切换。

// Same-document transition (Chrome 111+, Safari TP, Firefox behind a flag)
function navigate(newView) {
  if (!document.startViewTransition) {
    renderView(newView);
    return;
  }
  document.startViewTransition(() => renderView(newView));
}
/* Smooth crossfade by default; override per element */
::view-transition-old(*) { animation-duration: 200ms; }
::view-transition-new(*) { animation-duration: 200ms; }

跨文档视图转场(完整页面导航之间的转场)于 2024 年随 Chrome 126 发布,让 MPA 也能拥有 SPA 般的体验。搭配 prefers-reduced-motion 使用,让对动态效果敏感的用户可以立即切换,而不是播放动画。

滚动驱动动画

animation-timeline: scroll() 和 animation-timeline: view() 根据滚动位置驱动 CSS 动画,而不是根据墙钟时间。一个经典用途是在文章顶部显示进度指示器,并在滚动时逐渐填满。

@keyframes fill { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.read-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 3px;
  background: var(--color-primary);
  transform-origin: left;
  animation: fill linear;
  animation-timeline: scroll(root);
}

在基于 Chromium 的浏览器中已稳定支持(Chrome 115+、Edge);截至 2026-05,Safari 和 Firefox 尚未支持。将其用作渐进增强;提供 JS 回退方案,或接受其他浏览器上不那么醒目的基础效果。

不要使用产品导览

如果需要导览来解释 UI,说明 UI 设计有问题。请改用:

  • 能通过实际操作进行引导的空状态(“创建你的第一个项目”)
  • 渐进式展示,在功能变得相关时再显示
  • 首次使用后消失的行内提示
  • 无需配置即可使用的默认设置

URL

路径段应简短、易读且便于用户猜测。不要使用 UUID,也不要让查询参数堆成一团。

Good:  /projects/weather-app
       /settings/billing
       /docs/api/auth

Bad:   /projects/550e8400-e29b-41d4-a716-446655440000
       /app?view=settings&tab=billing&subsection=plan
       /dashboard#!/module/documents/list?filter=active
  • 使用根据用户提供的名称生成的路径段
  • 嵌套层级最多为 3 段
  • 让 URL 可以复制和分享,它们是产品的记忆

持久化且可恢复的状态

用户会离开,也会回来。请尊重这一点。

  • 将表单草稿状态保存到 localStorage 或服务器
  • 在后退导航时恢复滚动位置
  • 在不同会话之间保留筛选/排序选择
  • URL 编码当前视图状态,分享 URL 即可重现该视图
// Persist form state across sessions
function usePersistentForm(key, defaults) {
  const [state, setState] = useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : defaults;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(state));
  }, [key, state]);

  return [state, setState];
}

克制使用颜色

颜色不超过 3 种:一种主色、一种强调色、一种用于危险/破坏性操作的颜色。其他所有颜色都使用灰色系。

:root {
  --color-primary: #2563eb;
  --color-accent: #f59e0b;
  --color-danger: #ef4444;

  --gray-50: #fafafa;
  --gray-100: #f4f4f5;
  --gray-200: #e4e4e7;
  --gray-400: #a1a1aa;
  --gray-600: #52525b;
  --gray-900: #18181b;
}
  • 用不透明度和亮度建立层级,而不是增加新色相
  • 暗色模式使用相同的 3 种颜色,并反转灰色系
  • 如果需要第 4 种颜色,说明你是在弥补布局的不足

不显示滚动条

仅在用户主动滚动时显示。让内容感觉无穷无尽,而不是被困在某处。

/* Hide scrollbar across browsers */
.scroll-container {
  overflow-y: auto;
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;       /* IE/Edge */
}
.scroll-container::-webkit-scrollbar {
  display: none;                  /* Chrome/Safari */
}

用滚动阴影提示内容溢出,不要添加视觉装饰:

.scroll-shadow {
  background:
    linear-gradient(white 30%, transparent),
    linear-gradient(transparent, white 70%) 0 100%,
    radial-gradient(farthest-side at 50% 0, rgba(0,0,0,.15), transparent),
    radial-gradient(farthest-side at 50% 100%, rgba(0,0,0,.15), transparent) 0 100%;
  background-repeat: no-repeat;
  background-size: 100% 40px, 100% 40px, 100% 12px, 100% 12px;
  background-attachment: local, local, scroll, scroll;
}

导航深度

从任何位置出发,最多 3 步即可完成所有导航。如果用户需要点击超过 3 次才能到达目标位置,就简化层级。

  • 面包屑用于展示层级,而不是导航
  • 全局导航始终可见,在桌面端绝不隐藏在汉堡菜单后
  • 使用 Cmd+K / Ctrl+K,为高级用户提供备用入口
命令面板(Cmd+K)

任何包含多个页面的应用都需要命令面板。

// Minimal Cmd+K listener
useEffect(() => {
  function handleKeyDown(e) {
    if ((e.metaKey || e.ctrlKey) && e.key === "k") {
      e.preventDefault();
      setCommandPaletteOpen(true);
    }
  }
  document.addEventListener("keydown", handleKeyDown);
  return () => document.removeEventListener("keydown", handleKeyDown);
}, []);

保持面板简单:

  • 对页面名称、最近操作和设置进行模糊搜索
  • 在行内显示键盘快捷键
  • 最近使用的项目排在最前
  • 命令达到 20 个以上之前,不要分类

剪贴板

在用户预期的任何地方,都应支持复制和粘贴。

  • 代码、URL、API 密钥和 ID 支持一键复制
  • 支持从剪贴板粘贴内容到文件上传和图像字段
  • 复制后显示简短确认(“已复制!”),并自动消失
async function copyToClipboard(text, label = "Copied") {
  await navigator.clipboard.writeText(text);
  toast(label, { duration: 1500 });
}

点击目标

增大按钮和输入框的点击目标。WCAG 2.2(成功标准 2.5.8)规定的最低标准是 24×24 CSS 像素;Apple 人机界面指南以及大多数 iOS/Android 原生惯例建议采用舒适的 44×44 点。主要操作以 44px 为实际最低标准;次要控件(例如密集表格行中的图标)则以 24px 为绝对最低标准。

button, .btn, [role="button"] {
  min-height: 44px;
  min-width: 44px;
  padding: 10px 20px;
}

input, select, textarea {
  min-height: 44px;
  padding: 10px 12px;
  font-size: 16px;  /* Prevents iOS Safari zoom on focus */
}
  • 相邻的可点击元素之间至少留出 8px 间距
  • 纯图标按钮的内边距应大于带标签的按钮
  • 关键功能不能依赖悬停状态,因为触屏设备不支持悬停

轻松取消

一键取消。不施加压力,不用暗黑模式设计,也不说“确定要错过这个机会吗?”

  • 确认按钮旁始终显示取消按钮
  • 删除账户只需首次尝试即可完成
  • 退订只需点击一次,无需进入偏好设置中心
  • 降级流程无需联系客服

工具提示

尽量精简。工具提示意味着界面无法清楚地表达自身含义。

  • 仅用于纯图标按钮(提供标签)
  • 绝不用于已清晰可读的文本
  • 悬停 300ms 后再显示,不要立即出现
  • 滚动时消失
  • 绝不用工具提示传达关键信息

文案

使用主动语态。每句话最多 7 个词。像普通人一样说话,不要像法律文件。

Good:  "Project created"
       "Saved 2 minutes ago"
       "Delete this file?"

Bad:   "Your project has been successfully created!"
       "Changes were last saved approximately 2 minutes ago"
       "Are you sure you want to permanently delete this file? This action cannot be undone."
  • 按钮使用动词:“保存”“删除”“发送”,不要使用“提交”“确定”“确认”
  • 错误消息说明发生了什么,以及接下来该怎么做
  • 绝不责怪用户(将“输入无效”改为“请输入有效的电子邮件地址”)
  • 所有界面文案均使用句子大小写,绝不使用标题式大小写

视觉对齐

优先考虑视觉对齐,而非几何对齐。眼睛看到的不是像素,而是视觉重量。

  • 播放图标在圆形内向右偏移 2-3px,看起来才居中
  • 首字母大写的文本,其视觉左边缘应与边界框左侧对齐
  • 文本旁的图标应根据字形,垂直偏移 1-2px
  • 文本周围的内边距应在视觉上平衡,而非数学上相等;底部内边距通常比顶部多 1-2px
/* Geometric center ≠ optical center */
.play-button svg {
  transform: translateX(2px);
}

/* Visually balanced card padding */
.card {
  padding: 20px 24px 22px 24px;
}

从左到右的阅读流

针对从左到右的阅读习惯和 F 型扫描模式进行优化。

  • 将最重要的内容放在左上象限
  • 将主要操作放在右侧(视线读完一行后会落到这里)
  • 标签放在输入框上方,而不是旁边
  • 表格中最常被浏览的列放在最左侧
  • 正文不要居中对齐;除单行标题外,其他内容都左对齐

避免用户担心丢失内容

用户担心丢失工作。既要避免这种情况,也要让用户知道内容已保存。

  • 自动保存,并显示“已保存”指示和时间戳
  • 在破坏性操作后提供撤销功能(软删除,而非永久删除)
  • 离开页面时提示“你有未保存的更改”
  • 对超过一条推文长度的内容提供版本历史
  • 仅对不可逆操作进行确认,不要对常规操作进行确认
// Warn on unsaved changes
useEffect(() => {
  function handleBeforeUnload(e) {
    if (hasUnsavedChanges) {
      e.preventDefault();
      e.returnValue = "";
    }
  }
  window.addEventListener("beforeunload", handleBeforeUnload);
  return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, [hasUnsavedChanges]);

可下载的品牌素材

提供一个 /brand 或 /press 页面,供用户下载 SVG Logo 和品牌套件。别让用户截图保存你的 Logo。

  • 透明背景的 SVG Logo
  • 颜色代码(hex、RGB、HSL)
  • 字体名称和字重
  • 使用指南(最小尺寸、留白空间、禁用方式)
  • 一键下载 ZIP

检查清单

使用以下清单检查任何 Web UI:

  • 每次交互都在 100ms 内完成(或通过乐观更新模拟)
  • 没有产品导览或引导弹窗
  • URL 简短易读,不含 UUID
  • 状态在不同会话和页面重新加载后仍能保留
  • 颜色不超过 3 种(灰色除外)
  • 静止时不显示滚动条
  • 到达任何目标最多需要 3 步
  • SVG Logo 和品牌套件可供下载
  • 使用骨架屏加载器,而非旋转指示器
  • 代码、密钥和 URL 均可复制到剪贴板
  • 触控目标至少为 44px
  • 取消操作明确且只需点击一次
  • 提供 Cmd+K 命令面板
  • 仅为纯图标按钮提供工具提示
  • 文案使用主动语态,最多 7 个词
  • 采用视觉对齐,而非几何对齐
  • 内容遵循从左到右的 F 型阅读模式
  • 自动保存,并显示状态且支持撤销

评论

支持 Markdown