modern-web-guidance 实战:换掉旧写法

Agent 写前端总带着 2019 年的旧写法,缺一份可检索的现代基线。Google Chrome、Edge、Firefox 与 MDN 团队把 250 个现代 Web 特性的规则做成技能,本文讲接入 Claude Code / Codex、替换旧 API 和验证行为不变。

Bob

Bob

2026-11-12

modern-web-guidance 实战:换掉旧写法

先说结论:编程 Agent 写出 2019 年的前端代码,通常不是模型不会新 API,而是训练数据里旧代码太多,你的规则文件里又没有一份可检索的现代 Web 基线。modern-web-guidance 把 250 个现代 Web 特性的规则做成 skill 和 CLI:Agent 先 search,再 retrieve,只把当前任务需要的那一条指南放进上下文。

它适合用 Claude Code、Codex 或 Cursor 写前端,却总要手工改回现代写法的开发者,也适合维护 AGENTS.md、想让多个 Agent 遵守同一套前端约定的团队。

本文按「它是什么、怎么接、换哪几个旧写法、怎么验证、怎么防止规则膨胀」走一遍。事实核对到 2026 年 11 月 11 日,命令和指南 ID 见文末来源。

它提供的不是文档站,而是按需检索的规则层

modern-web-guidance 是 Google Chrome、Microsoft Edge、Mozilla Firefox 和 MDN 团队共同维护的一套 Agent 技能,代码用 Apache 2.0,指南内容用 CC-BY 4.0。它覆盖 250 个现代 Web 特性,分成用户体验、CSS 布局、性能、表单与 UI、无障碍、内置 AI 六类。这个数字来自 README 的 “250 modern web features”。

内容形态是一个 SKILL.md 加一组 guides/*.md。每个指南有 name、description、web-feature-ids 和关联指南,正文给可直接用的 HTML、CSS、JS 模式,再补浏览器支持、降级方案和坑。

CLI 有四个常用动作:install、search、retrieve、legacy-scan。检索用本地离线 embedding 加 BM25,不需要联网或 API key,也不把代码发出去。README 说 npm 包自包含,用 npx 是为了拿最新内容,不是每次走云端。

它省上下文靠按需取,不是把指南写短。README 标注 modern-web-guidance 技能包约 205 个 Token,完整指南在 retrieve 时才进入上下文。对几万 Token 的规则文件来说,这个差别比“写得更精炼”重要。

接进 Claude Code 和 Codex:插件优先,规则文件只留三行

最省事的接入方式是装成插件或技能,不要手工复制指南。官方文档给的 Claude Code 命令是:

claude plugin install modern-web-guidance@claude-plugins-official

README 还给了另一条路径,适合想自己管 marketplace 的人:

/plugin marketplace add GoogleChrome/modern-web-guidance
/plugin install modern-web-guidance@googlechrome

Codex 的插件命令是:

codex plugin marketplace add GoogleChrome/modern-web-guidance
codex plugin add modern-web-guidance@googlechrome

不想走插件,也可以先跑交互式安装向导:

npx modern-web-guidance@latest install

装完之后,让 Agent 主动调用它的是 SKILL.md 的 description。原文写得很硬:所有 HTML、CSS 和客户端 JS 任务都要先执行,因为 Web API 变化快,训练权重里有过时写法。触发词包括弹窗、表格、深色模式、容器查询、视图过渡、滚动动画、表单校验、无障碍、Core Web Vitals、图片优化、passkeys、HTML 清洗、Web Components、日期时区、内置 AI。它明确排除后端、CI/CD、Docker、Python/Go、Git 和纯工具配置。

如果你的 Agent 只读 AGENTS.md 或 CLAUDE.md,不认插件,就加三条,不要更多:

## Web UI
Before writing or changing HTML/CSS/client-side JS, run:
npx -y modern-web-guidance@latest search "<2-5 word capability>"
Then retrieve the matching guide ID and follow it. If no guide matches, proceed.
Browser target: Baseline Widely available. No polyfill over 20 lines without approval.

这三行只规定什么时候查、怎么查、浏览器底线是什么。不要把 250 条指南或整个目录树粘贴进规则文件。设计系统、组件库和例外才应该留在 AGENTS.md。

已有代码库可以先跑一次扫描:

npx modern-web-guidance@latest legacy-scan . --json

注意它的边界。SKILL.md 明确说,legacy-scan 只覆盖大约 20% 的指南,也就是有确定性旧写法规则的那一部分。扫出 0 条不等于代码已经现代化,只等于这 20% 没命中。

四个最常被 Agent 抄回去的旧写法

下面四个不是随机挑的。它们在 legacy-scan 里有确定性规则,也在前端 review 里反复出现。

1. 弹窗:手写遮罩、焦点陷阱、Esc 监听,换成 <dialog>

旧写法通常是一个 div.modal,加 role="dialog"、aria-modal="true"、手写 focus trap、Esc 监听和遮罩点击判断。焦点返回和移动端返回手势还经常漏。

原生 <dialog> 把这些交给浏览器。指南给的确认弹窗写法是:

<button type="button" command="show-modal" commandfor="confirm">删除</button>

<dialog id="confirm" aria-labelledby="confirm-title" closedby="any">
  <h2 id="confirm-title">删除这条记录?</h2>
  <form method="dialog">
    <button value="cancel" autofocus>取消</button>
    <button value="delete">删除</button>
  </form>
</dialog>

指南明确要求不要再加 role="dialog"、aria-modal、focus trap 或 inert,原生 modal 已经提供这些语义,并把焦点限制在弹窗内,还带 top layer、::backdrop、Esc 和返回手势,以及关闭后的焦点返回。也不要用 show() 或 open 打开 modal。

closedby 控制点击外部是否关闭:closedby="any" 允许轻触关闭,不写则只响应 Esc 和返回手势,表单有未保存内容时用 closedby="none" 并在 cancel 事件里做确认。还要补两条 CSS,阻止滚动穿透:

dialog:modal { overscroll-behavior: contain; }
dialog::backdrop { overflow: hidden; overscroll-behavior: contain; }

2. 表单::invalid 和 scrollHeight,换成 :user-invalid 和 field-sizing

旧写法一进页面就给所有空必填项标红,或者用 :not(:focus):invalid 这种组合绕开“还没输入就报错”。现代做法是 :user-invalid:只有用户修改并离开字段,或者尝试提交之后,才命中错误状态。指南给的样式是:

input:user-invalid {
  border-color: #d93025;
  background-color: #fce8e6;
}
input:user-invalid + .error-msg { display: block; }
input:user-valid { border-color: #188038; }

自动撑高 textarea 也不需要监听 input 再读 scrollHeight。CSS 的 field-sizing: content 让控件按内容 sizing,配好上下限即可:

textarea {
  field-sizing: content;
  width: 100%;
  min-block-size: 3lh;
  max-block-size: 10lh;
}
input, select {
  field-sizing: content;
  width: auto;
  min-inline-size: 15ch;
  max-inline-size: 50ch;
}

两个坑:全局的 width: 100% 或固定 height 会覆盖 field-sizing,要改回 width: auto 和 height: auto;Grid 或 Flex 拉伸子项时,控件可能“不肯缩”,需要加 align-self: start 或 justify-self: start。

3. 深色模式:复制两套颜色,换成 color-scheme 加 light-dark()

旧写法是在 @media (prefers-color-scheme: dark) 里把背景、文字、边框颜色再写一遍。项目一大,两套 token 就开始漂移。指南建议在 HTML 里先声明:

<meta name="color-scheme" content="light dark">

然后在 :root 上设 color-scheme: light dark,用 light-dark() 定义成对的 token:

:root {
  color-scheme: light dark;
  --bg: light-dark(#ffffff, #111111);
  --fg: light-dark(#171321, #eeeeee);
}
body { background: var(--bg); color: var(--fg); }

指南强调 color-scheme 要放在 :root 或 html,不要只放 body,否则根滚动条、表单控件和初始画布可能仍是亮色。meta 是为了尽早告诉浏览器支持两种主题,减少首屏白闪。prefers-color-scheme 适合做上下文微调,不适合复制整套颜色。

4. 滚动出现动画:AOS、WOW、ScrollReveal,换成 CSS 滚动驱动动画

旧写法是引入 AOS、WOW 或 ScrollReveal,初始化后由 JS 监听滚动、计算位置、加 class。指南的替换是 CSS 的 animation-timeline: view() 加 animation-range: entry, exit:

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow { from { scale: 0.5; } }
    @keyframes shrink { to { scale: 0.5; } }
    .scroller > * {
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: view(inline);
      animation-range: entry, exit;
    }
  }
}

关键是 @supports 和 prefers-reduced-motion 两层保护。不支持的浏览器直接看到最终状态,不会因为动画没跑而把内容藏起来;开启减少动态效果的用户也不会被强制看动画。很多旧库正好相反,先给元素 opacity: 0,等 JS 加 class,JS 一失败内容就消失。

怎么验证「行为没变」:把感觉变成断言

换 API 最容易犯的错,是只看截图觉得“看起来一样”。下面几个断言可以直接放进 Playwright,覆盖弹窗、表单、深色模式和滚动动画。

test('dialog closes with Esc and returns focus', async ({ page }) => {
  await page.goto('/');
  const trigger = page.getByRole('button', { name: '删除' });
  await trigger.click();
  const dialog = page.locator('dialog');
  await expect(dialog).toHaveJSProperty('open', true);
  await page.keyboard.press('Escape');
  await expect(dialog).toHaveJSProperty('open', false);
  await expect(trigger).toBeFocused();
});

test('validation waits for interaction', async ({ page }) => {
  const input = page.getByLabel('Email Address');
  await input.fill('not-an-email');
  expect(await input.evaluate(el => el.matches(':user-invalid'))).toBe(false);
  await input.blur();
  expect(await input.evaluate(el => el.matches(':user-invalid'))).toBe(true);
});

深色模式用 page.emulateMedia({ colorScheme: 'dark' }),断言 body 的计算背景色等于暗色 token。滚动动画滚到元素位置,断言最终 opacity 为 1;再把 reducedMotion 设为 reduce,确认元素仍可见。弹窗补一条:打开后按 Tab,焦点不应跑到弹窗外。

如果多个 Agent 并行改前端,验证结果要能回到任务上,而不是散在各自的终端里。HiFox 的执行记录把任务、被指派的 Agent、Runtime 和结果留在同一处,审阅者不用重建一个私有会话。机制见执行文档。

最后再跑一次 legacy-scan 对比修改前后。它适合当审计清单,不适合当唯一门禁:覆盖只有约 20%,而且静态规则会误报。指南本身要求逐条看 keepWhen / dropWhen,把 fallback、跨文件 helper、第三方代码和形似代码排除掉,再决定改不改。

规则变多之后:三层,不要一份 AGENTS.md 打天下

第一层是技能和 CLI。它负责“知道”,按任务检索,不常驻上下文。第二层是 AGENTS.md 或 CLAUDE.md,只放项目策略:目标浏览器、降级预算、设计系统、组件库约定、明确例外。第三层是 CI 和 review,负责“证明”:legacy-scan、Playwright 断言、人工看 diff。

检索要克制。SKILL.md 要求查询用 2 到 5 个词,描述一个能力,不要带框架名和业务名词。查 "light dismiss dialog",不要查 "React 设置弹窗点外面关闭";弹窗和表格拆成两次 search。只有同一任务确实需要多条指南,才用逗号把 ID 传给一次 retrieve。

实验性能力要显式放行。仓库支持在项目根放 .mwgrc,默认不放开 Origin Trial 和 volatile 特性;需要时用 set-allow-origin-trials 或 set-allow-volatile-features 打开,并把 .mwgrc 提交到仓库,让团队看到这是一次明确决定,而不是某个 Agent 顺手打开。

冲突要有顺序:用户明确要求优先于项目 AGENTS.md,项目 AGENTS.md 优先于指南默认值。指南和组件库冲突时,保留项目约定,但在 AGENTS.md 写一行“例外、原因、日期”,不要让下一个 Agent 再猜。最怕两套规则都很长,Agent 每次只读到一半,最后按“平均值”生成代码。

版本也要管。npx @latest 适合交互式试用,CI 里应该固定版本。截至 2026 年 11 月 11 日,npm 上的包是 1.0.0,package.json 要求 Node 22 以上;在 Node 20.18.3 上执行会收到 EBADENGINE 警告。升级用 npx modern-web-guidance@latest update,升级后重跑一遍你的断言,不要假设指南更新一定兼容现有代码。

三个坑,先知道再上线

第一,“modern”不等于跨浏览器。指南里有不少 Chrome 先行或仍在稳定的特性。HN 讨论里有人列过一串“Chrome 稳定、Firefox 和 Safari 没有”的清单,包括 scroll-target-group、:target-current、interestfor、text-fit、calc-size()、interpolate-size 和内置 AI 的几个 API。这份清单是评论者列的,本文没有逐项复核,但它提醒你:指南的 Baseline 数据是参考,目标浏览器要你自己定。把 Browser target 写进 AGENTS.md,跑自己的浏览器矩阵。

第二,技能激活是概率事件。SKILL.md 写的是 MANDATORY,但 LLM 仍可能不查、只读一半,或者查了不照做。Adrian Roselli 在 2026 年 5 月的测试里,用 Antigravity 加 modern-web-guidance 生成官网首页第一个示例,动画在 Firefox 不工作,也没达到 WCAG;他引用的 Google issue #875 也承认,指南越分散,Agent 越不容易把相关条目都捡起来。结论是:别把指南当保证。

第三,安装和运维有成本。npx 需要网络,会拉一个 npm 包;包要求 Node 22 以上;默认开启匿名遥测,收集安装次数、调用 Agent 名、指南检索 ID 和 CLI 搜索词,不收集原始 prompt,可用 DISABLE_TELEMETRY=1 关掉。企业环境还要考虑 npm 供应链和版本锁定。

结论:先迁移一个组件,再谈全站

不要一上来就全站替换。选一个边界清楚的组件,比如确认弹窗或邮箱校验,开分支,跑 search 和 retrieve,按指南改完,补三条 Playwright 断言,再对比行为和代码量。这个流程跑通后,把三行规则写进 AGENTS.md,最后才用 legacy-scan 扫全仓库。

modern-web-guidance 解决的是“Agent 不知道现代 Web 怎么写”。它不解决“Agent 说它写了、但行为变了”。后者要靠你的测试、review 和任务记录。如果这件事要交给多个 Agent 并行做,可以在 HiFox 里建任务,把目标浏览器、验收断言和停止点写进任务,再让 Agent 执行。规则、执行和结果放在同一处,下一次迁移才有可复用的证据。

资料来源:GoogleChrome/modern-web-guidance、官方文档、SKILL.md、指南源码、HN Show HN 讨论、HN 评论中的兼容性清单、Adrian Roselli 的批评、npm 包。

Explore more

Claude Fable 5.2 对比 Opus 5.5:哪些编程任务值得切换

Claude Fable 5.2 对比 Opus 5.5:哪些编程任务值得切换

Fable 5.2 的厂商基准高于 Opus 5.5,但价格也翻倍。按任务类型决定何时从 Opus 升级到 Fable,并用接受率、返工和审阅时间判断切换是否真的划算。

2026-11-12

防范 AGENTS.md 劫持编程 Agent:5 步指南

防范 AGENTS.md 劫持编程 Agent:5 步指南

下载目录里的一份恶意 AGENTS.md 就能劫持 Codex 或 Claude Code,篡改配置并窃取 OAuth 令牌。本文拆解真实攻击链与五步团队防护法:安全启动目录、指令文件审阅、拒绝配置改写、开启沙盒审批与配置巡检。

2026-11-11

Claude Code 免费做视频:cinecode 实测

Claude Code 免费做视频:cinecode 实测

借助 MIT 开源技能 cinecode,Claude Code 可以直接用代码编写分镜、在无头 Chrome 中逐帧渲染并合成带音效的 MP4 视频。本文详解安装配置、M1 Pro 渲染实测、Token 消耗及团队任务协作流程。

2026-11-11