Skip to main content
Back to contributions
Pull Request
In Review
24.2K

Port useKeyWithMouseEvents a11y rule to HTML

biomejs/biome

Ported the useKeyWithMouseEvents accessibility rule from JSX to HTML, ensuring mouse event handlers are paired with keyboard equivalents for keyboard-only users

The Problem

Biome’s useKeyWithMouseEvents rule enforces that mouse event handlers are paired with keyboard equivalents, but it only worked in JSX contexts. With Biome’s HTML linter now operational, HTML, Vue, Svelte, and Astro files lacked this critical keyboard accessibility check.

This matters because WCAG 2.1.1 requires that all functionality be operable through a keyboard interface. Users with physical disabilities who cannot use a mouse, assistive technology users, and screen reader users all depend on keyboard alternatives. The SCR2 technique specifically defines the required pairings: onmouseover with onfocus, and onmouseout with onblur.

Invalid HTML that would go undetected:

<div onmouseover="handleMouseOver()"></div>
<div onmouseout="handleMouseOut()"></div>

The Solution

I ported the useKeyWithMouseEvents accessibility rule from JSX to HTML as part of issue #8155, which tracks the effort to port all 30+ a11y rules to HTML.

The rule enforces two event pairings:

  • onmouseover must be accompanied by onfocus - so hover-triggered actions are accessible via keyboard focus
  • onmouseout must be accompanied by onblur - so mouse-leave actions are accessible via keyboard blur

Key implementation details:

  • Case-insensitive attribute matching - In HTML, onmouseover, OnMouseOver, and ONMOUSEOVER are all equivalent
  • Source-type aware component detection - In Vue/Svelte/Astro, PascalCase elements (e.g., <MyComponent>) are custom components and are skipped. In HTML, all elements are native regardless of casing
  • Wrong pair detection - Having onmouseover with onblur (instead of onfocus) is correctly flagged

Valid HTML examples:

<div onmouseover="handleMouseOver()" onfocus="handleFocus()"></div>
<div onmouseout="handleMouseOut()" onblur="handleBlur()"></div>

Research

The implementation was informed by reviewing:

The eslint rule supports configurable hoverInHandlers/hoverOutHandlers options to extend checking beyond the default pair (e.g., onMouseEnter, onPointerOver). The existing Biome JSX rule does not implement these extended options, so this HTML port matches the current Biome JSX behavior.

Files Changed

FileDescription
crates/biome_html_analyze/src/lint/a11y/use_key_with_mouse_events.rsNew rule implementation with rustdoc documentation
.changeset/add-use-key-with-mouse-events-html.mdChangeset file (minor version bump)
crates/biome_html_analyze/tests/specs/a11y/useKeyWithMouseEvents/HTML test fixtures (valid + invalid)
crates/biome_html_analyze/tests/specs/a11y/useKeyWithMouseEvents/vue/Vue-specific test cases
crates/biome_html_analyze/tests/specs/a11y/useKeyWithMouseEvents/svelte/Svelte-specific test cases
crates/biome_html_analyze/tests/specs/a11y/useKeyWithMouseEvents/astro/Astro-specific test cases

Technical Details

The rule uses Ast<AnyHtmlElement> as its query type, matching all HTML elements. Attribute lookup via find_attribute_by_name is already case-insensitive in the HTML syntax crate. The HtmlFileSource source type is used to distinguish HTML files (where all elements are native) from framework files (where PascalCase indicates custom components).

Test coverage includes 8 snapshot tests across HTML, Vue, Svelte, and Astro with cases for:

  • Missing keyboard pair entirely
  • Having the wrong keyboard pair (e.g., onmouseover + onblur)
  • Case-insensitive attribute variants
  • Self-closing elements
  • Different element types (<a>, <button>, <span>, <input>)
  • Empty attribute values
  • PascalCase custom components (framework-specific)

Timeline

DateAction
2026-03-22Claimed useKeyWithMouseEvents on issue #8155
2026-03-22Draft PR submitted with implementation and tests