The Problem
Anchor elements (<a>) are fundamental navigation components on the web, but they’re often created without accessible content. Screen reader users rely on link text to understand where a link leads. Empty anchors, anchors with only whitespace, or anchors where all content is hidden with aria-hidden create significant accessibility barriers.
The useAnchorContent rule existed for JSX but was missing for HTML, leaving a gap in accessibility validation for HTML, Vue, Svelte, and Astro files.
Invalid HTML that would go undetected:
<!-- Empty anchor -->
<a></a>
<!-- Whitespace-only content -->
<a> </a>
<!-- Content hidden from screen readers -->
<a aria-hidden="true">Click here</a>
<!-- All children hidden -->
<a><span aria-hidden="true">Link text</span></a>
The Solution
I ported the useAnchorContent accessibility rule from JSX to HTML as part of issue #8155. This was my second rule from the umbrella issue, following the same nested tag checking pattern from my useMediaCaption implementation (#8742).
The rule enforces that anchor elements have accessible content, with support for multiple ways to provide accessible names:
- Text content - Direct text or text in child elements
aria-labelattribute - Explicit accessible nametitleattribute - Alternative accessible name- Visible child elements - Elements not hidden with
aria-hidden
Valid HTML examples:
<!-- Text content -->
<a>Click here</a>
<!-- Nested text content -->
<a><span>Navigate to home</span></a>
<!-- aria-label provides accessible name -->
<a aria-label="Navigate to dashboard"></a>
<!-- title provides accessible name -->
<a title="Go to settings"></a>
<!-- Some content visible despite hidden sibling -->
<a><span aria-hidden="true"></span>Visible text</a>
Unsafe Fix
When an anchor has aria-hidden="true" on the element itself (hiding the entire link), the rule provides an unsafe fix to remove the attribute, making the content accessible again.
Files Changed
| File | Description |
|---|---|
crates/biome_html_analyze/src/lint/a11y/use_anchor_content.rs | New rule implementation with comprehensive rustdoc documentation |
crates/biome_html_analyze/src/lint/a11y.rs | Rule registration in the a11y module |
.changeset/add-use-anchor-content-html.md | Changeset file (minor version bump) |
crates/biome_html_analyze/tests/specs/a11y/useAnchorContent/ | Test fixtures for HTML |
crates/biome_html_analyze/tests/specs/a11y/useAnchorContent/vue/ | Vue-specific test cases |
crates/biome_html_analyze/tests/specs/a11y/useAnchorContent/svelte/ | Svelte-specific test cases |
crates/biome_html_analyze/tests/specs/a11y/useAnchorContent/astro/ | Astro-specific test cases |
Technical Details
The implementation includes:
- Recursive content checking - Traverses nested elements to find accessible content
aria-hiddendetection - Checks both the anchor and all descendants for hidden content- Source-type aware matching - Case-insensitive in HTML files, case-sensitive in Vue/Svelte/Astro (PascalCase = custom component)
- Self-closing anchor detection - Handles
<a />syntax - Empty/whitespace attribute validation -
aria-label=""ortitle=" "are correctly flagged as invalid - Void element validation -
<img>requires non-emptyalt,<br>/<hr>/<wbr>never accessible,<input type="hidden">not accessible
Timeline
| Date | Action |
|---|---|
| 2026-01-14 | PR submitted with core implementation |
| 2026-01-14 | Restructured framework tests to use subdirectories (matching codebase conventions) |
| 2026-01-15 | Fixed source-type aware tag name matching per maintainer review |
| 2026-01-15 | Made void element accessible content check stricter per CodeRabbit review |
| 2026-01-15 | PR approved and merged by @ematipico |