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:
onmouseovermust be accompanied byonfocus- so hover-triggered actions are accessible via keyboard focusonmouseoutmust be accompanied byonblur- so mouse-leave actions are accessible via keyboard blur
Key implementation details:
- Case-insensitive attribute matching - In HTML,
onmouseover,OnMouseOver, andONMOUSEOVERare 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
onmouseoverwithonblur(instead ofonfocus) 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-plugin-jsx-a11y source - the original rule Biome sources from
- The Biome JSX implementation - existing behavior to match
- The WCAG SCR2 technique - the accessibility standard defining the event pairings
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
| File | Description |
|---|---|
crates/biome_html_analyze/src/lint/a11y/use_key_with_mouse_events.rs | New rule implementation with rustdoc documentation |
.changeset/add-use-key-with-mouse-events-html.md | Changeset 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
| Date | Action |
|---|---|
| 2026-03-22 | Claimed useKeyWithMouseEvents on issue #8155 |
| 2026-03-22 | Draft PR submitted with implementation and tests |