GitHub 开源 alt text 质量检查插件:从确定性规则到模型评审
Your alt text passes automated checks. That doesn’t mean it’s any good.
做无障碍或自动化检查的同学必看,这篇把确定性规则与模型评审的取舍讲透了,还给了可复用的 prompt 思路和布局判断代码,值得收藏。
More than one in four images on the web’s most popular home pages have alt text that’s missing, vague, or copied from adjacent images.
网络上最受欢迎的主页中,超过四分之一的图片的替代文本缺失、含糊不清,或从相邻图片复制而来。
That’s from WebAIM’s 2026 WebAIM Million report, which found that alt text,an HTML attribute containing text describing the content of an image, was missing on 16.2% of images across the top million home pages. Among the images that did have alt text, another 10.8% provided an undescriptive attribute, such as alt="image", a raw filename, or a description duplicated from a neighbor.
这是来自WebAIM的2026年WebAIM百万报告,该报告发现,在排名前百万的主页中,16.2%的图片缺少替代文本,这是一种包含描述图片内容的文本的HTML属性。在确实有替代文本的图片中,另有10.8%提供了不具描述性的属性,如alt="image"、原始文件名,或从相邻图片复制的描述。
While automated tooling reliably flags missing alt text, it isn’t as good at fixing poorly written alt text. Most alt text checkers test whether an accessible name for an image exists, not whether the provided alt text says anything useful about the associated image, and that’s a deliberate design choice: a quality-oriented rule with false positives is a rule teams switch off. So alt="IMG_2847.png" passes. So does the same alt="3/5 stars" on five different star-shaped icons.
虽然自动化工具能可靠地标记缺失的替代文本,但在修复写得不好的替代文本方面并不擅长。大多数替代文本检查器测试的是图片是否存在可访问的名称,而不是提供的替代文本是否对相关图片有任何有用的描述,这是一个深思熟虑的设计选择:一个带有误报的质量导向规则是团队会关闭的规则。所以alt="IMG_2847.png"通过检查。五个不同的星形图标上相同的alt="3/5 stars"也通过。
We built an alt text plugin for the GitHub Accessibility Scanner to help improve your alt text. This post covers where we drew the line between what a checker can prove and what it can only suspect, why our worst bug turned out to be a layout problem rather than a parsing one, and what changed once we let a model into the loop.
我们为GitHub无障碍扫描器构建了一个替代文本插件,以帮助改进您的替代文本。这篇文章涵盖了我们在检查器能证明什么和只能怀疑什么之间划定的界限,为什么我们最严重的错误最终是布局问题而非解析问题,以及一旦我们让模型参与进来后发生了什么变化。
If you’re building automated checks of your own, for accessibility or otherwise, the tradeoffs should transfer.
如果您正在构建自己的自动化检查,无论是为了无障碍还是其他目的,这些权衡应该可以转移。
Proving a string is wrong without seeing the picture
在不看图片的情况下证明字符串是错误的
Presence of alt text is an objective fact; the attribute is there or it isn’t. Quality is often a judgment call. A machine can’t prove whether a sentence adequately describes a picture in context from markup.
替代文本的存在是一个客观事实;属性存在或不存在。质量通常是一个主观判断。机器无法从标记中证明一个句子是否充分描述了上下文中的图片。
However, not all quality is subjective. There’s several checks you can perform based on the alt text alone, with no need to consult the image content:
然而,并非所有质量都是主观的。有几种检查可以仅基于替代文本进行,无需参考图片内容:
- The attribute is absent (not empty) or whitespace-only.
- The alt is a filename, such as hero.png, IMG_2847.jpg.
- The alt is a placeholder somebody meant to replace, such as TODO, tbd.
- The alt is one generic word naming the medium instead of the content, such as image, logo, chart.
- The same alt repeats across adjacent images.
- 属性缺失(非空)或仅包含空白字符。
- 替代文本是文件名,如hero.png、IMG_2847.jpg。
- 替代文本是某人打算替换的占位符,如TODO、tbd。
- 替代文本是一个通用词,命名媒介而非内容,如image、logo、chart。
- 相同的替代文本在相邻图片中重复出现。
Every one of those is a claim about a string, and that became our dividing line. Five deterministic rules run by default which need no credentials for running AI models or network calls. One opt-in rule calls a model with provided image content and surrounding context, for judgments an alt text string can’t support on its own.
这些每一条都是关于字符串的声明,这成了我们的分界线。默认运行的五条确定性规则不需要运行AI模型或网络调用的凭据。一个可选规则调用模型,使用提供的图片内容和周围上下文,用于替代文本字符串本身无法支持的判断。
First, we had to determine which images to judge on a scanned webpage. We use Playwright’s role-based locator rather than querySelectorAll('img'), so anything not included in the browser’s accessibility tree drops out, including anything carrying alt="". That last exclusion matters most. An empty alt is the author explicitly saying the image is decorative, and flagging it would punish exactly the behavior you want to encourage.
首先,我们必须确定在扫描的网页上要评判哪些图片。我们使用Playwright的基于角色的定位器,而不是querySelectorAll('img'),因此浏览器可访问性树中未包含的任何内容都会被排除,包括带有alt=""的内容。最后一项排除最为关键。空的alt是作者明确表示图片是装饰性的,标记它会惩罚你恰恰想要鼓励的行为。
So, how strict should it be? A quality checker lives or dies on false positives, so we chose closed sets over clever heuristics. The vague-alt rule normalizes a string, then checks it against a curated list of words that carry no information on their own. It fires only on an exact match:
那么,应该有多严格呢?质量检查器的成败取决于误报率,因此我们选择封闭集合而非巧妙的启发式方法。模糊alt规则会规范化字符串,然后对照一个精心策划的列表检查,这些词本身不携带信息。它只在完全匹配时触发:
- alt="image" gets flagged.
- alt="image of the login screen with the SSO button highlighted" doesn’t.
- alt="image"会被标记。
- alt="image of the login screen with the SSO button highlighted"不会被标记。
Rules this literal miss plenty of bad alt text. We took the miss over the false positive, because a reliable checker that developers enable beats one that gets switched off.
如此字面的规则会漏掉许多糟糕的alt文本。我们选择漏报而非误报,因为一个可靠的检查器,开发者会启用,胜过会被关闭的。
Repetition is a layout problem, not a DOM problem
重复是布局问题,不是DOM问题
Repeated alt text presented an interesting problem. Picture a row of five star-shaped icons that each say "3/5 stars". A screen reader user hears the same thing five times and learns nothing new from four of them.
重复的alt文本提出了一个有趣的问题。想象一行五个星形图标,每个都写着“3/5星”。屏幕阅读器用户会听到相同内容五次,其中四次不会学到新东西。
Our first version walked the images in document order and flagged any run sharing the same normalized alt. It caught things it shouldn’t have. For example, a footer “GitHub” logo and a header “GitHub” logo might sit next to each other in the extracted list but nowhere near each other on screen, so nobody experiences them as a group.
我们的第一个版本按文档顺序遍历图片,并标记任何共享相同规范化alt的连续序列。它捕获了本不该捕获的内容。例如,页脚的“GitHub”标志和页眉的“GitHub”标志可能在提取的列表中相邻,但在屏幕上相距甚远,因此没有人会将它们视为一组。
What matters is where images land on screen, not where they sit in the markup. So the rule now checks page layout, and only extends a run when the gap between two bounding boxes is small compared to the boxes themselves:
重要的是图片在屏幕上的位置,而不是它们在标记中的位置。因此,规则现在检查页面布局,并且仅当两个边界框之间的间隙相对于框本身较小时才扩展连续序列:
const gap = Math.max(horizontalGap, verticalGap)
const largerDim = Math.max(a.boundingBox.width, a.boundingBox.height,
b.boundingBox.width, b.boundingBox.height)
return gap > GAP_MULTIPLIER * largerDimconst gap = Math.max(horizontalGap, verticalGap)
const largerDim = Math.max(a.boundingBox.width, a.boundingBox.height,
b.boundingBox.width, b.boundingBox.height)
return gap > GAP_MULTIPLIER * largerDimTwo details worth noting:
有两个值得注意的细节:
- The multiplier is a judgment call, not a number we derived from anything. It’s the kind of value you tune against real pages instead of trusting from a spec.
- When either image has no measurable box, the check fails open and the run continues. A missing finding is invisible; a wrong one isn’t.
- 乘数是一个判断决定,不是我们从任何东西推导出的数字。这是那种你根据真实页面调整的值,而不是信任规范。
- 当任一图片没有可测量的框时,检查会失败并继续运行。缺失的发现是看不见的;错误的发现则不是。
Getting a model to act like a reviewer, not a critic
让模型像审稿人而非批评家一样行动
Deterministic rules only need the alt string. Anything smarter needs to know what the page is about, and none of that is tracked by the image element. Whether alt="a smiling person" is fine depends entirely on what surrounds it: on a generic mood shot, it’s probably works. But under a heading where a specific person is named, it doesn’t provide enough detail.
确定性规则只需要alt字符串。任何更智能的处理都需要知道页面内容,而图像元素并未跟踪这些信息。alt="一个微笑的人"是否合适完全取决于其周围环境:在通用的情绪照片中,可能没问题。但在标题下指定了特定人物时,它提供的细节就不够充分。
In our optional alt-text-qualitycheck, we extract page context alongside each image: the nearest heading, the page title, any <figcaption>, whether the image sits inside a link or button, and up to 600 characters of nearby prose.
在我们可选的alt文本质量检查中,我们提取每个图像周围的页面上下文:最近的标题、页面标题、任何<figcaption>、图像是否位于链接或按钮内,以及最多600个字符的邻近文本。
The link signal matters most, because when an image is a link’s only content, its alt becomes the link’s accessible name. The right alt then names the destination instead of describing the picture.
链接信号最为重要,因为当图像是链接的唯一内容时,其alt文本成为链接的可访问名称。正确的alt文本应指向目标而非描述图片。
One caution: The plugin only records that an image sits inside a link. We don’t check whether it’s the link’s only content, which is the part that actually turns alt into a link name. So right now both cases look identical to the model.
一个注意事项:插件仅记录图像位于链接内。我们不检查它是否是链接的唯一内容,而这正是将alt转换为链接名称的关键部分。因此,目前两种情况对模型来说看起来相同。
That context, the alt, and the image go to a vision model through GitHub Models. Our failure modes were rarely the model misreading a picture. They were the model having opinions. Given perfectly good alt text, our first version of the checker would suggest different alt text, because “could this be better?” is a question a language model always answers yes to. Every image becomes a finding, so the signal disappears.
该上下文、alt文本和图像通过GitHub Models发送给视觉模型。我们的失败模式很少是模型误读图片,而是模型有自己的观点。面对完全合适的alt文本,我们第一版检查器会建议不同的alt文本,因为“能否更好?”是语言模型总是回答“是”的问题。每个图像都成为发现项,信号因此消失。
Three changes fixed it:
三项更改解决了这个问题:
- A decision procedure instead of an instruction. The prompt walks four ordered steps, stops at the first that matches, and emits that step’s verdict: decorative, redundant with a caption, functional, or informative.
- Explicit anti-nitpick rules. Trust the author’s framing. Separate redundant prefixes (“Image of…”) from semantic ones (“Photograph of…”). Treat a short alt as correct when the surrounding prose already analyzes the image.
- Structured output with a forced field order, so reasoning is generated before verdict and the model has to build an argument before it picks a label.
- 决策程序而非指令。提示词按顺序执行四个步骤,在第一个匹配处停止,并输出该步骤的判定:装饰性、与标题冗余、功能性或信息性。
- 明确的防吹毛求疵规则。信任作者的框架。区分冗余前缀(如“图像…”)和语义前缀(如“照片…”)。当周围文本已分析图像时,将简短alt视为正确。
- 结构化输出,强制字段顺序,使推理在判定之前生成,模型必须在选择标签前构建论证。
None of that makes the model unfailingly correct. It makes it consistent enough to iterate against. The repository carries an offline grading harness built from published teaching material: WebAIM, the W3C images tutorial, and POET. The rule and the harness share one prompt, so what you tune offline is what runs in CI. That harness only tests the model’s judgment, though, not the whole pipeline. A case can score perfectly there and never reach the model in a real scan.
这些都不意味着模型永远正确。它只是足够一致,以便进行迭代。仓库中带有一个离线评分工具,基于已发布的教材构建:WebAIM、W3C 图像教程和 POET。规则和评分工具共享同一个提示,因此你在离线调整的内容就是 CI 中运行的内容。不过,该评分工具只测试模型的判断,而不测试整个流程。一个案例在那里可能得分完美,但在实际扫描中却永远不会到达模型。
Sending images to a model is a privacy and cost decision
将图像发送给模型是一个隐私和成本决策
The moment a check calls an external model with webpage data, it stops being just a lint rule and requires careful data flow design. A few things follow from that:
当检查调用外部模型处理网页数据时,它就不再仅仅是一个 lint 规则,而需要仔细的数据流设计。由此产生以下几点:
- The rule is off by default. It won’t run unless you deliberately enable it in your plugin configuration, and it needs a token with access to GitHub Models.
- 该规则默认关闭。除非你在插件配置中特意启用它,否则它不会运行,并且它需要一个有权访问 GitHub Models 的令牌。
更进一步:量化金融体系
看懂新闻只是起点——沿量化金融路径,把它变成能交付的工程能力