Skip to content

github/accessibility-scanner-alt-text-plugin

Alt-Text Plugin for the Accessibility Scanner

The Alt-Text Plugin is a plugin for the AI-powered Accessibility Scanner that flags low-quality alt text on images. It complements axe-core's built-in image-alt rule, helping teams ship images with descriptive, screen-reader-friendly alternative text.

The plugin helps teams:

  • 🖼️ Catch vague, generic, or filler alt text ("image", "photo", "icon")
  • 📛 Catch raw filenames used as alt text ("IMG_1234.png")
  • 🔁 Catch runs of adjacent images that share the same alt text
  • 🚧 Catch boilerplate placeholder alt text ("todo", "tbd", "fixme")
  • ♿ Catch images missing an alt attribute entirely (without flagging intentional decorative alt="")

⚠️ Note: This plugin is in active development alongside the a11y scanner's public preview. New rules are still being added and end-to-end integration with the scanner's issue-filing workflow is still maturing. Always review filed issues before acting on them.


The plugin inherits the a11y scanner's general FAQ — see the link above for questions about scanning, caching, GitHub Enterprise, and more. Plugin-specific questions are answered inline below.


Background

This plugin catches low-quality alt text that axe-core's built-in image-alt rule cannot — vague single-word alt, raw filenames, runs of duplicate alt, and never-filled-in placeholders. The scope is intentionally narrow: deterministic, heuristic checks on <img> elements only. Non-<img> role="img" elements, decorative alt="", and hidden subtrees are filtered out before any rule runs.

The project is under active development alongside the scanner's public preview. Roadmap and open work live in this repo's Issues. See CONTRIBUTING.md for how to contribute, including local setup, expected checks, and PR conventions.


Requirements

To use the Alt-Text Plugin, you'll need:

  • The AI-powered Accessibility Scanner wired into a GitHub Actions workflow in your repository
  • The plugin's source files copied into ./.github/scanner-plugins/alt-text-scan/ in the repository that runs the scanner workflow (see Getting started)
  • Everything required to run the scanner itself (Actions enabled, Issues enabled, a GH_TOKEN PAT — see the scanner README for the full list)

This plugin is currently consumed from source: copy the plugin files into the repository that runs the scanner workflow.

To develop the plugin locally, you'll also need:

  • Node.js matching the engines field in package.json — currently ^22.13.0 || ^24 || ^26
  • npm (ships with Node)

Getting started

1. Add the plugin to your scanner repository

Following the conventions in the scanner's PLUGINS.md, each plugin lives under ./.github/scanner-plugins/<plugin-name>/ in the repository that runs the scanner workflow. Drop the plugin's index.ts and supporting files into ./.github/scanner-plugins/alt-text-scan/.

.github/
└── scanner-plugins/
    └── alt-text-scan/
        ├── index.ts
        ├── src/
        ├── schema/
        └── config.json   # optional

📚 Learn more


2. Enable the plugin in your workflow

Add "alt-text-scan" to the scanner action's scans input. If you don't already set scans, keep "axe" in the list too — the scanner only runs Axe by default, and providing any value at all opts you out of that default.

name: Accessibility Scanner
on: workflow_dispatch

jobs:
  accessibility_scanner:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6 # Required so the scanner can read your repo's scanner-plugins/ directory
      - uses: github/accessibility-scanner@v3
        with:
          urls: |
            https://example.com
          repository: REPLACE_THIS/REPLACE_THIS
          token: ${{ secrets.GH_TOKEN }}
          cache_key: REPLACE_THIS
          scans: '["axe", "alt-text-scan"]' # Add "alt-text-scan" to enable this plugin

👉 Update all REPLACE_THIS placeholders with your actual values. See the scanner's Action inputs for the full list.

📚 Learn more


3. Run your first scan

Trigger your scanner workflow manually or on its configured schedule. The plugin runs on every URL the scanner visits, extracts each image exposed to assistive technology, and emits a finding for every rule violation. The scanner then turns those findings into GitHub issues.

📚 Learn more


Rules

The plugin runs every extracted image through an append-only registry of rules. Each rule returns a finding when an image fails its criteria, and the scanner turns each finding into an issue.

Rule ID Fires when Example (flagged)
Missing alt missing-alt-text The alt attribute is absent (null) or whitespace-only (" "). alt="" is treated as intentional decorative use and is not flagged. <img src="cat.png">
<img src="cat.png" alt=" ">
Vague alt vague-alt-text The alt text is one of a curated set of generic single words (image, photo, icon, logo, screenshot, chart, untitled, etc.) or short filler phrases (an image of, a photo of). Normalization is applied before matching: case-insensitive, whitespace-collapsed, surrounding punctuation stripped. <img alt="image">
<img alt="An image of">
<img alt="PHOTO.">
Filename as alt filename-alt-text The alt text ends in a common image file extension (.png, .jpg, .jpeg, .gif, .svg, .webp, .bmp, .ico). <img alt="IMG_1234.png">
<img alt="Screenshot 2024-04-28.jpg">
Repeated alt repeated-alt-text Two or more adjacent images on the rendered page share the same normalized alt text. Useful for patterns like five star icons all labeled "3/5 stars". Five consecutive <img alt="3/5 stars"> elements
Placeholder alt placeholder-alt-text The alt text matches a known boilerplate string that signals it was never written (todo, tbd, fixme, placeholder, alt text, insert alt text, image alt). Normalization is applied before matching. <img alt="TODO">
<img alt="insert alt text">

Image extraction

Before rules run, the plugin extracts images from the page through Playwright's accessibility tree (page.getByRole('img')) and narrows the result to actual <img> elements. The following are filtered out automatically and never reach the rules:

  • Non-<img> elements with role="img" (e.g. <svg role="img">, <div role="img">) — this plugin's rules only apply to HTML <img> tags
  • Images inside aria-hidden="true" subtrees
  • Images inside display: none or visibility: hidden subtrees
  • Decorative images with alt="" (implicit role="presentation")

Overlap with Axe

The scanner's built-in Axe scan includes a rule called image-alt that catches missing and whitespace-only alt attributes. If you have both "axe" and "alt-text-scan" enabled, the same image may be flagged by both. The other four rules in this plugin (vague-alt-text, filename-alt-text, repeated-alt-text, placeholder-alt-text) are unique to the plugin and don't overlap with Axe.


Output

When a rule fires, the plugin emits a finding with the following shape (matching the scanner's Finding type):

  • scannerType — always 'alt-text-scan'; identifies which plugin produced the finding
  • ruleId — the ID of the rule that fired (e.g. 'vague-alt-text')
  • url — the page URL where the image was found
  • html — the offending <img> element's outer HTML
  • problemShort — one-sentence description of what's wrong, including the offending alt text where applicable
  • problemUrl — link to the relevant WCAG technique or W3C tutorial
  • solutionShort — one-sentence description of how to fix it
  • solutionLong — optional longer explanation when one sentence isn't enough

The scanner uses these fields to file or update a GitHub issue.


Configuration

To override the default enabled state of one or more rules, add a config.json file next to the plugin's index.ts in your scanner repository:

.github/scanner-plugins/alt-text-scan/
├── index.ts
└── config.json   ← optional
{
  "rules": {
    "repeated-alt-text": false,
    "placeholder-alt-text": false
  }
}
  • Each key under rules is a rule ID from the Rules table above; the value is true (run the rule) or false (skip it).
  • Rules you don't list keep their default behavior. Today every rule defaults to enabled.
  • Unknown rule IDs and non-boolean values are logged as warnings and ignored (typo guard).
  • A missing or malformed config.json causes the plugin to run with all defaults.
  • The plugin reads the config once at startup, not per URL.

A JSON Schema is published at schema/config.schema.json. Add a $schema line at the top of your config.json to get autocomplete, hover docs, and inline validation in editors that support JSON Schema (VS Code, JetBrains IDEs, etc.):

{
  "$schema": "https://raw.githubusercontent.com/github/accessibility-scanner-alt-text-plugin/main/schema/config.schema.json",
  "rules": {
    "repeated-alt-text": false
  }
}

The $schema line is optional and is ignored by the plugin at runtime.


Development

Local setup

git clone https://github.com/github/accessibility-scanner-alt-text-plugin.git
cd accessibility-scanner-alt-text-plugin
npm ci

Common scripts

Script What it does
npm run test Runs the Vitest unit suite once
npm run test:watch Runs Vitest in watch mode
npm run typecheck Runs tsc --noEmit against the whole project
npm run lint Runs ESLint
npm run format Rewrites files with Prettier
npm run format:check Reports formatting violations without writing

Pull requests trigger two CI workflows: lint.yml runs lint and format:check on Node 24, and test.yml runs typecheck and test across Node 22, 24, and 26.

Project layout

index.ts                    # Plugin entry point: exports `name` and the default scan function
src/
  config.ts                 # Loads & validates .github/scanner-plugins/alt-text-scan/config.json
  extract.ts                # Pulls visible <img> records from a Playwright page
  findings.ts               # Translates each RuleResult into the scanner's Finding shape
  rules/
    index.ts                # Append-only rule registry
    missing-alt-text.ts
    vague-alt-text.ts
    filename-alt-text.ts
    placeholder-alt-text.ts
    repeated-alt-text.ts
  utils/
    normalize-alt-text.ts   # Lowercase, trim, collapse whitespace, strip punctuation
  types.ts                  # Rule, RuleContext, RuleResult, ImageRecord, Finding
tests/
  extract.test.ts           # Playwright-driven tests for the image extractor
  example-site.test.ts      # Runs the plugin against the example/site-with-errors fixture
  fixtures/                 # Static HTML fixtures used by the extractor tests
  unit/
    *.test.ts               # One file per rule, plus config.test.ts for the loader
  utils/
    helpers.ts              # makeImage() and evaluateAlts() — shared across rule tests

Adding a new rule

  1. Create src/rules/<rule-name>.ts exporting a Rule (see src/types.ts for the shape — id, problemUrl, and evaluate(context)). Filenames under src/ and tests/ must be kebab-case; ESLint's check-file/filename-naming-convention rule enforces this.
  2. Import the rule in src/rules/index.ts and append it to allRules. The registry is append-only — don't reorder existing rules.
  3. Add a tests/unit/<rule-name>.test.ts file. Use evaluateAlts(alts, rule) and makeImage(overrides) from tests/utils/helpers.ts for the common cases; construct an explicit RuleContext only when you need control over src or other per-image fields.
  4. Run npm run test && npm run typecheck && npm run lint locally before opening a PR. CI re-runs them.

Important

Image extraction happens once per page, before any rule runs, so every rule sees the same filtered list regardless of which rules are enabled. Don't reach into the DOM from a rule — work from the ImageRecord[] the rule's context provides.


Feedback

💬 We welcome your feedback! To submit feedback or report issues, please open an issue in this repository. For broader feedback on the a11y scanner itself, file it in the scanner repository.


License

📄 This project is licensed under the terms of the MIT open source license. See the LICENSE file for the full terms.

Maintainers

🔧 Maintained alongside the AI-powered Accessibility Scanner. See CODEOWNERS for the responsible team.

Support

❓ For support, please open an issue in this repository. See SUPPORT.md for support expectations, or the scanner's SUPPORT document for guidance that applies across the project.

Acknowledgement

✨ Built on top of Playwright, Vitest, and the broader open-source accessibility tooling ecosystem. Thank you to everyone contributing rules, tests, and review.

About

A plugin for the GitHub Accessibility Scanner that flags low-quality alt text on images.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors