Skip to main content
Back to contributions
Pull Request
Merged
23.2K

Port useAnchorContent a11y rule to HTML

biomejs/biome

Ported the useAnchorContent accessibility rule from JSX to HTML, ensuring anchor elements have accessible content for screen readers

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-label attribute - Explicit accessible name
  • title attribute - 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

FileDescription
crates/biome_html_analyze/src/lint/a11y/use_anchor_content.rsNew rule implementation with comprehensive rustdoc documentation
crates/biome_html_analyze/src/lint/a11y.rsRule registration in the a11y module
.changeset/add-use-anchor-content-html.mdChangeset 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-hidden detection - 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="" or title=" " are correctly flagged as invalid
  • Void element validation - <img> requires non-empty alt, <br>/<hr>/<wbr> never accessible, <input type="hidden"> not accessible

Timeline

DateAction
2026-01-14PR submitted with core implementation
2026-01-14Restructured framework tests to use subdirectories (matching codebase conventions)
2026-01-15Fixed source-type aware tag name matching per maintainer review
2026-01-15Made void element accessible content check stricter per CodeRabbit review
2026-01-15PR approved and merged by @ematipico