@its-thepoe/alt-text 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +159 -0
- package/package.json +13 -0
- package/reference.md +235 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: alt-text
|
|
3
|
+
description: >-
|
|
4
|
+
Scans images in a codebase for missing, empty, or low-quality alt text and
|
|
5
|
+
generates context-aware alt text drafts. Presents findings in a review table
|
|
6
|
+
for user approval before applying changes. Use when the user says "add alt
|
|
7
|
+
text", "fix alt text", "accessibility audit images", "a11y images", or
|
|
8
|
+
"missing alt". Supports Next.js (next/image), React, Astro, and Vue. Not for
|
|
9
|
+
full WCAG audits beyond images — use accessibility-review if present.
|
|
10
|
+
argument-hint: "[scope: repo | folder path | cms]"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Alt text
|
|
14
|
+
|
|
15
|
+
**Orchestrator skill:** scan → classify → draft → **review table (blocking)** → apply → verify.
|
|
16
|
+
|
|
17
|
+
Deep rules, grep patterns, decision tree, and examples: [reference.md](reference.md).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Scope
|
|
22
|
+
|
|
23
|
+
If the user did not specify, ask once:
|
|
24
|
+
|
|
25
|
+
| Scope | Meaning |
|
|
26
|
+
|------|---------|
|
|
27
|
+
| **repo** | Entire project (default if they say “whole repo” or give no path) |
|
|
28
|
+
| **folder** | Limit to one directory (e.g. `src/components/`) |
|
|
29
|
+
| **cms** | Content / data only — MDX, Markdown, JSON, YAML, CMS-shaped fields |
|
|
30
|
+
|
|
31
|
+
Respect `node_modules`, build output, `.next`, `dist`, and large binary dirs — do not treat assets inside them as “source” unless the user insists.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Step 1 — Detect stack
|
|
36
|
+
|
|
37
|
+
Read `package.json` (and if present `astro.config.*`, `nuxt.config.*`, `vite.config.*`) to infer which image APIs apply. If multiple stacks exist, scan with **all** relevant patterns from [reference.md](reference.md#framework-image-patterns-and-greps).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Step 2 — Scan for image usages
|
|
42
|
+
|
|
43
|
+
Framework-aware search (see [reference.md](reference.md#framework-image-patterns-and-greps)):
|
|
44
|
+
|
|
45
|
+
- **Next.js:** `next/image`, `next/legacy/image`, `<Image`, `fill`, `sizes` patterns with `alt`
|
|
46
|
+
- **React:** `<img`, team `<Image>` wrappers
|
|
47
|
+
- **Astro:** `astro:assets`, `<Image`, `<Picture`
|
|
48
|
+
- **Vue / Nuxt:** `<img`, `:alt`, `v-bind:alt`
|
|
49
|
+
- **MDX / MD:** ``, `<img`
|
|
50
|
+
- **CMS-shaped content:** image URLs plus `alt`, `caption`, `asset`, `fields` (JSON/YAML)
|
|
51
|
+
|
|
52
|
+
For each hit, classify:
|
|
53
|
+
|
|
54
|
+
- **missing** — no `alt` attribute / no markdown alt / no CMS alt field when required
|
|
55
|
+
- **empty** — `alt=""` or equivalent — only OK if [decorative per decision tree](reference.md#w3c-inspired-decision-tree)
|
|
56
|
+
- **present** — has non-empty alt; may still be **low quality** (filename, “image”, redundant, too long)
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Step 3 — Decision tree (per image)
|
|
61
|
+
|
|
62
|
+
Apply [W3C-inspired decision tree](reference.md#w3c-inspired-decision-tree) **before** drafting:
|
|
63
|
+
|
|
64
|
+
- Decorative / redundant with adjacent text → propose **`alt=""`** (or keep empty) and explain
|
|
65
|
+
- Functional (inside link, acts as control) → alt describes **destination or action**
|
|
66
|
+
- Informative → short, contextual description
|
|
67
|
+
- Complex (chart, diagram) → short **summary** in alt; flag need for extended text elsewhere
|
|
68
|
+
- Text in image → transcribe when that text is unique; if duplicated nearby → may be `alt=""`
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Step 4 — Draft alt text
|
|
73
|
+
|
|
74
|
+
For every row that needs a new or better alt:
|
|
75
|
+
|
|
76
|
+
- Use **surrounding component / page / heading context** and filename as weak hints only
|
|
77
|
+
- **~125 characters** or less unless complex-image exception; front-load important words
|
|
78
|
+
- **Do not** start with “image of”, “photo of”, “picture of”, or “graphic of”
|
|
79
|
+
- Assign **confidence:** `HIGH` (clear role in context) or `LOW` (decorative vs informative unclear, brand tone unknown, unclear subject)
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Step 5 — Review table (mandatory stop)
|
|
84
|
+
|
|
85
|
+
Output **one** markdown table with **all** proposed changes (including “keep `alt=""`” rows you want the user to confirm):
|
|
86
|
+
|
|
87
|
+
| # | Location | Framework / kind | Current | Proposed | Confidence | Type |
|
|
88
|
+
|---|----------|------------------|---------|----------|------------|------|
|
|
89
|
+
| 1 | `path:line` | Next `<Image>` | (missing) | … | HIGH | informative |
|
|
90
|
+
|
|
91
|
+
**STOP.** Wait for explicit user approval: all, per-row edits, or skip list. Do **not** edit files until they respond.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Step 6 — Apply (after approval)
|
|
96
|
+
|
|
97
|
+
Apply only approved rows using correct syntax:
|
|
98
|
+
|
|
99
|
+
- Next `<Image alt="…" />`
|
|
100
|
+
- React `<img alt="…" />` (or wrapper’s `alt` prop)
|
|
101
|
+
- Astro `<Image alt="…" />` / `<Picture>` slots per Astro docs
|
|
102
|
+
- Vue static images: plain `alt="…"`; use `:alt` only when value is **dynamic** from CMS/props
|
|
103
|
+
- Markdown: ``
|
|
104
|
+
- CMS JSON/YAML: set the project’s canonical alt field (see [reference.md](reference.md#cms-content-patterns))
|
|
105
|
+
|
|
106
|
+
Preserve formatting and conventions of each file.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Step 7 — Verify
|
|
111
|
+
|
|
112
|
+
Re-scan changed files only:
|
|
113
|
+
|
|
114
|
+
- No `<img` / `<Image` without `alt` where one is required
|
|
115
|
+
- No accidental removal of intentional empty alts the user wanted to keep
|
|
116
|
+
- Markdown / MDX image syntax still valid
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Invariants
|
|
121
|
+
|
|
122
|
+
- Never remove or overwrite **valid** alt text without explicit user approval in the review step
|
|
123
|
+
- **Never** apply code or content edits before the review table and user confirmation
|
|
124
|
+
- Never set `alt=""` on an image that is **informative or functional** in context
|
|
125
|
+
- Never add “image of” / “photo of” / “picture of” filler to drafts
|
|
126
|
+
- Decorative images with confirmed `alt=""` are **not** defects
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Handoffs / out of scope
|
|
131
|
+
|
|
132
|
+
- Full-site WCAG audits, focus order, ARIA beyond images → **accessibility-review** (or project a11y skill)
|
|
133
|
+
- Adding new CMS schema fields, migrations, or Studio config so `alt` exists → **manual / separate task**; note in table as blocked
|
|
134
|
+
- No images in scope → report “no images found” and stop
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Install this skill (copy or symlink the folder)
|
|
139
|
+
|
|
140
|
+
| Product | Personal (all projects) | Project-only |
|
|
141
|
+
|--------|-------------------------|--------------|
|
|
142
|
+
| **Cursor** | `~/.cursor/skills/alt-text/` | `<repo>/.cursor/skills/alt-text/` |
|
|
143
|
+
| **Claude Code** | `~/.claude/skills/alt-text/` | `<repo>/.claude/skills/alt-text/` |
|
|
144
|
+
| **OpenCode** | `~/.config/opencode/skills/alt-text/` | `<repo>/.opencode/skills/alt-text/` |
|
|
145
|
+
| **Windsurf** | `~/.codeium/windsurf/skills/alt-text/` | `<repo>/.windsurf/skills/alt-text/` |
|
|
146
|
+
| **Antigravity / Gemini** | Confirm in product docs (often `~/.gemini/skills/`) | `<repo>/.agent/skills/alt-text/` (common) |
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
SKILL_SRC="/path/to/skills/alt-text"
|
|
150
|
+
mkdir -p ~/.cursor/skills ~/.claude/skills ~/.config/opencode/skills ~/.codeium/windsurf/skills
|
|
151
|
+
ln -sf "$SKILL_SRC" ~/.cursor/skills/alt-text
|
|
152
|
+
ln -sf "$SKILL_SRC" ~/.claude/skills/alt-text
|
|
153
|
+
ln -sf "$SKILL_SRC" ~/.config/opencode/skills/alt-text
|
|
154
|
+
ln -sf "$SKILL_SRC" ~/.codeium/windsurf/skills/alt-text
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Reload or restart the agent after install.
|
|
158
|
+
|
|
159
|
+
**Standards:** [WCAG 2 — Non-text content](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html), [W3C Images Tutorial](https://www.w3.org/WAI/tutorials/images/), [WebAIM Alternative Text](https://webaim.org/techniques/alttext/).
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@its-thepoe/alt-text",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Agent Skill: scan and draft alt text for images (Next.js, React, Astro, Vue, CMS) with review table before apply.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": ["agent-skills", "accessibility", "alt-text", "a11y", "cursor"],
|
|
7
|
+
"files": ["SKILL.md", "reference.md", "package.json"],
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./SKILL.md",
|
|
10
|
+
"./SKILL.md": "./SKILL.md",
|
|
11
|
+
"./reference.md": "./reference.md"
|
|
12
|
+
}
|
|
13
|
+
}
|
package/reference.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Alt text — reference
|
|
2
|
+
|
|
3
|
+
Supporting detail for [SKILL.md](SKILL.md). Keep `SKILL.md` as the entry; load this file when scanning, classifying, or drafting.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core quality rules (evidence-aligned)
|
|
8
|
+
|
|
9
|
+
Great alt text is:
|
|
10
|
+
|
|
11
|
+
| Principle | Meaning |
|
|
12
|
+
|-----------|---------|
|
|
13
|
+
| **Accurate & equivalent** | Same meaning and **function** as the image in *this* context |
|
|
14
|
+
| **Succinct** | Usually a few words; aim **< ~125 characters** unless complex-image exception |
|
|
15
|
+
| **Contextual** | Depends on role on the page — never describe in isolation |
|
|
16
|
+
| **Non-redundant** | No repeating adjacent headings/captions; no “image of …” |
|
|
17
|
+
| **Purpose-driven** | Why it’s there — not literal pixels unless appearance *is* the point |
|
|
18
|
+
| **Always present** | Every `<img>` needs an `alt` attribute — **even if empty** (`alt=""`) for decorative |
|
|
19
|
+
|
|
20
|
+
**Pitfalls:** filenames as alt, keyword stuffing, subjective words (“beautiful”), leaving alt off when content exists, decorative images with verbose alt.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## W3C-inspired decision tree
|
|
25
|
+
|
|
26
|
+
Before writing alt, answer in order (adapted from [W3C Images Tutorial](https://www.w3.org/WAI/tutorials/images/decisiontree/)):
|
|
27
|
+
|
|
28
|
+
1. **Does the image contain text?**
|
|
29
|
+
- Text is already nearby as real text → often **`alt=""`** (decorative/redundant)
|
|
30
|
+
- Text only for visual treatment → **`alt=""`**
|
|
31
|
+
- Text has a **function** (e.g. icon) → describe **function**
|
|
32
|
+
- Text is **unique** to the image → **transcribe** the text (concisely)
|
|
33
|
+
|
|
34
|
+
2. **Is the image a link or button and would purpose be unclear without it?**
|
|
35
|
+
- Yes → describe **destination or action** (e.g. “View full report”, not “picture of document”)
|
|
36
|
+
|
|
37
|
+
3. **Does the image add meaning beyond decoration?**
|
|
38
|
+
- Yes, simple photo/graphic → brief **meaning**
|
|
39
|
+
- Yes, complex (chart, diagram, map) → **short summary** in `alt`; put details in body, `<figcaption>`, or linked description
|
|
40
|
+
- Yes, but **redundant** with adjacent text → **`alt=""`**
|
|
41
|
+
|
|
42
|
+
4. **Purely decorative or no added meaning?**
|
|
43
|
+
- Yes → **`alt=""`** (or prefer CSS `background-image` for purely decorative)
|
|
44
|
+
|
|
45
|
+
**Unclear?** Prefer a short purpose statement in context, or ask the user (mark **LOW** confidence).
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Framework image patterns and greps
|
|
50
|
+
|
|
51
|
+
Use `rg` (ripgrep) or project search. Adjust globs to the repo’s `src/`, `app/`, `content/` layouts.
|
|
52
|
+
|
|
53
|
+
**Exclude:** `node_modules/`, `dist/`, `.next/`, `build/`, `coverage/`, lockfiles unless user wants them.
|
|
54
|
+
|
|
55
|
+
### Next.js (`next/image`)
|
|
56
|
+
|
|
57
|
+
- Import: `from "next/image"` or `from "next/legacy/image"`
|
|
58
|
+
- JSX: `<Image` with props `src`, `alt`, often `width`/`height` or `fill`
|
|
59
|
+
|
|
60
|
+
**Example greps:**
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
rg '<Image\b' --glob '*.{tsx,jsx,js}'
|
|
64
|
+
rg 'next/image' --glob '*.{tsx,jsx,js}' # import path
|
|
65
|
+
rg 'next/legacy/image' --glob '*.{tsx,jsx,js}'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Rules:**
|
|
69
|
+
|
|
70
|
+
- `alt` is **required** for meaningful images; use `alt=""` when [decorative](#w3c-inspired-decision-tree).
|
|
71
|
+
- For `fill` layouts, `alt` rules are the same — size props don’t change a11y.
|
|
72
|
+
|
|
73
|
+
### React (non-Next)
|
|
74
|
+
|
|
75
|
+
- `<img` lowercase in JSX
|
|
76
|
+
- Design-system `Image` components: same as whatever prop they map to `alt` on the DOM
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
rg '<img\b' --glob '*.{tsx,jsx,js}'
|
|
80
|
+
rg '<Image\b' --glob '*.{tsx,jsx,js}'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If a wrapper passes `alt` through, set the wrapper’s `alt` prop per its docs.
|
|
84
|
+
|
|
85
|
+
### Astro
|
|
86
|
+
|
|
87
|
+
- `@astrojs/image` / `astro:assets`: `<Image`, `<Picture`
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
rg '<Image\b|<Picture\b' --glob '**/*.{astro,tsx,jsx}'
|
|
91
|
+
rg 'astro:assets|astrojs/image' --glob '**/astro.config.*'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Rules:**
|
|
95
|
+
|
|
96
|
+
- Set `alt` on Astro `<Image>`; for `<Picture>`, set alt on the primary image per [Astro image docs](https://docs.astro.build/en/guides/images/).
|
|
97
|
+
|
|
98
|
+
### Vue / Nuxt
|
|
99
|
+
|
|
100
|
+
- `<img alt="static">` vs `:alt="dynamicFromCms"` — static marketing copy should be a plain string; bind only when data-driven.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
rg '<img\b' --glob '*.{vue,tsx,jsx}'
|
|
104
|
+
rg '(:alt|v-bind:alt)\s*=' --glob '*.vue'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Markdown / MDX
|
|
108
|
+
|
|
109
|
+
- ``
|
|
110
|
+
- Raw HTML `<img>` inside MDX
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
rg '!\[[^\]]*\]\([^)]+\)' --glob '*.{md,mdx}'
|
|
114
|
+
rg '<img\b' --glob '*.{md,mdx}'
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Rules:**
|
|
118
|
+
|
|
119
|
+
- **Empty** markdown alt `` is usually an a11y gap — propose a real alt or move to decorative CSS.
|
|
120
|
+
|
|
121
|
+
### Plain HTML templates
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
rg '<img\b' --glob '*.{html,htm,vue,astro,svelte}'
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## CMS content patterns
|
|
130
|
+
|
|
131
|
+
**Scope `cms`:** focus on `content/`, `data/`, `posts/`, `sanity-export/`, `*.json` frontmatter blocks, etc.
|
|
132
|
+
|
|
133
|
+
### Common shapes
|
|
134
|
+
|
|
135
|
+
| Shape | Where alt lives |
|
|
136
|
+
|-------|-----------------|
|
|
137
|
+
| **Frontmatter** | `image: { src, alt }` or `coverAlt:` |
|
|
138
|
+
| **Portable Text / rich text** | Image blocks with `asset` + `alt` field — **field name varies** |
|
|
139
|
+
| **Headless JSON** | `fields.file.url` + sibling `fields.title` or `description` — confirm which field is a11y-safe |
|
|
140
|
+
| **YAML collections** | Same as JSON; watch indentation |
|
|
141
|
+
|
|
142
|
+
### Sanity (illustrative)
|
|
143
|
+
|
|
144
|
+
- Image type often has `asset` + optional `alt`. If the schema has no alt, **flag as blocked** — schema/workflow change needed, not a one-line content fix.
|
|
145
|
+
|
|
146
|
+
### Contentful / similar
|
|
147
|
+
|
|
148
|
+
- Asset **description** vs **title** — prefer whatever your frontend maps to `alt`. If unclear, mark **LOW** and ask.
|
|
149
|
+
|
|
150
|
+
**Rule:** Never invent new CMS keys; match the repo’s existing content contract. If missing, add a table row: “Blocked — no alt field in schema/content model.”
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Framework-specific implementation notes
|
|
155
|
+
|
|
156
|
+
### Next.js `<Image>`
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import Image from "next/image";
|
|
160
|
+
|
|
161
|
+
<Image src={hero} alt="Concise description of the photo in context." width={800} height={600} priority />;
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Decorative:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
<Image src={texture} alt="" width={32} height={32} aria-hidden />;
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Use `aria-hidden` only when the image is **purely** decorative; pair with `alt=""` per team policy — don’t double-announce.
|
|
171
|
+
|
|
172
|
+
### React `<img>`
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
<img src={url} alt="Meaningful short description" loading="lazy" decoding="async" />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Astro `<Image>` (astro:assets)
|
|
179
|
+
|
|
180
|
+
Follow project imports; always pass `alt`:
|
|
181
|
+
|
|
182
|
+
```astro
|
|
183
|
+
<Image src={import('../assets/photo.png')} alt="Summary appropriate to the page." />
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Vue
|
|
187
|
+
|
|
188
|
+
```vue
|
|
189
|
+
<img :src="url" :alt="imageAltFromCms" />
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Static marketing:
|
|
193
|
+
|
|
194
|
+
```vue
|
|
195
|
+
<img src="/hero.jpg" alt="Customers collaborating on a dashboard in an office." />
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Good vs bad examples
|
|
201
|
+
|
|
202
|
+
| Type | Bad | Why | Good |
|
|
203
|
+
|------|-----|-----|------|
|
|
204
|
+
| Informative (article) | `Image of a smiling woman in a spacesuit` | “Image of”, too generic | `Astronaut Ellen Ochoa` |
|
|
205
|
+
| Functional (logo link) | `Shield with longhorn` | Appearance, not destination | `University of Texas at Austin homepage` |
|
|
206
|
+
| Decorative | `Decorative swirl separator` | Noise for SRs | `alt=""` |
|
|
207
|
+
| Complex | `Sales chart` (vague) | No insight | `Line chart: revenue rising from $1.2M to $2.8M, Q1–Q4 2025` + data table in page |
|
|
208
|
+
| Text in image | `Blue infographic` | Ignores text | `Infographic: 40% faster loading; 25% more engagement; accessible to all users` |
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Quick checklist (every image)
|
|
213
|
+
|
|
214
|
+
- [ ] Has an `alt` attribute (or markdown equivalent)?
|
|
215
|
+
- [ ] Accurate to **context and purpose**?
|
|
216
|
+
- [ ] Succinct (~125 chars or less unless complex)?
|
|
217
|
+
- [ ] No redundancy with adjacent text?
|
|
218
|
+
- [ ] No “image of …” / filename-as-alt?
|
|
219
|
+
- [ ] Makes sense when read aloud with surrounding content?
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Verification aids (optional)
|
|
224
|
+
|
|
225
|
+
- **axe DevTools**, **WAVE**, **Lighthouse** “image alt” audits
|
|
226
|
+
- Screen readers: VoiceOver (macOS), NVDA (Windows)
|
|
227
|
+
- Re-grep after edits for `<Image` / `<img` without `alt` in touched files
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Further reading
|
|
232
|
+
|
|
233
|
+
- [W3C WAI: An alt Decision Tree](https://www.w3.org/WAI/tutorials/images/decisiontree/)
|
|
234
|
+
- [WebAIM: Alternative Text](https://webaim.org/techniques/alttext/)
|
|
235
|
+
- [WCAG 2.2 Understanding 1.1.1 Non-text Content](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html)
|