Skip to content

feat(html): port noAriaHiddenOnFocusable a11y rule to HTML#9496

Open
aviraldua93 wants to merge 1 commit intobiomejs:nextfrom
aviraldua93:feat/html-no-aria-hidden-on-focusable
Open

feat(html): port noAriaHiddenOnFocusable a11y rule to HTML#9496
aviraldua93 wants to merge 1 commit intobiomejs:nextfrom
aviraldua93:feat/html-no-aria-hidden-on-focusable

Conversation

@aviraldua93
Copy link

Summary

Ports the noAriaHiddenOnFocusable lint rule from JSX to HTML, as part of the umbrella issue #8155.

What the rule does

Enforces that aria-hidden="true" is not set on focusable elements. A focusable element with aria-hidden can be reached by keyboard but is invisible to screen readers, causing confusion.

What counts as focusable

  • Elements with non-negative tabindex (e.g., tabindex="0")
  • Natively focusable elements: <button>, <input>, <select>, <textarea>, <details>, <summary>, <a href="...">, <area href="...">
  • Elements with contenteditable="true"

Exemptions

  • aria-hidden="true" on non-focusable elements (div, span, etc.) is valid
  • tabindex="-1" with aria-hidden="true" is valid (intentionally removed from tab order)

Fix

Provides an unsafe fix that removes the aria-hidden attribute.

Test plan

  • valid.html: aria-hidden on non-focusable elements, focusable elements without aria-hidden, negative tabindex with aria-hidden
  • invalid.html: button/a/input/select/textarea with aria-hidden, tabindex="0" with aria-hidden, contenteditable with aria-hidden

Closes part of #8155.

@changeset-bot
Copy link

changeset-bot bot commented Mar 16, 2026

🦋 Changeset detected

Latest commit: 1ffe6b4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added A-Linter Area: linter L-HTML Language: HTML and super languages labels Mar 16, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

Walkthrough

This PR introduces a new HTML accessibility lint rule NoAriaHiddenOnFocusable (recommended, Error level with unsafe fixer) that flags aria-hidden="true" on focusable elements. The rule identifies focusability through native interactive tags (button, input, select, textarea), links with href attributes, elements with non-negative tabindex, and contenteditable elements. Violations trigger a diagnostic with an unsafe fix that removes the problematic aria-hidden attribute. Test fixtures provide coverage for valid and invalid scenarios across various interactive element types.

Possibly related PRs

Suggested reviewers

  • ematipico
  • dyc3
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: porting the noAriaHiddenOnFocusable rule from JSX to HTML.
Description check ✅ Passed The description is directly related to the changeset, providing clear context about what the rule does, focusability criteria, exemptions, and test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs`:
- Around line 155-164: The check treating all inputs as natively focusable is
causing false positives for <input type="hidden">; update the conditional that
currently uses name_matches("input") (in the focusability check inside
no_aria_hidden_on_focusable.rs) to exclude inputs whose type attribute equals
"hidden" (case-insensitive). Implement the check inline or add a helper like
is_hidden_input(node) that reads the "type" attribute and returns true for
"hidden", and only treat the element as focusable when name_matches("input") &&
!is_hidden_input(node).
- Around line 171-180: has_contenteditable_true currently returns false when the
contenteditable attribute exists without a value or is an empty string; change
the logic to treat an attribute present with no value or an empty value as
truthy per the HTML spec: call element.find_attribute_by_name("contenteditable")
and match on the Option, returning false only when the attribute is None, but
when Some(attr) return true if attr.value() is None OR (attr.value() is Some(v)
and v.trim().eq_ignore_ascii_case("false") is false); update
has_contenteditable_true (and reference AnyHtmlElement/find_attribute_by_name
and attr.value()) accordingly so missing-value and empty-string cases are
detected as true.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7ff8e9ec-a6c9-43ea-b217-637a1aecbdcb

📥 Commits

Reviewing files that changed from the base of the PR and between 8f47f06 and a91f417.

📒 Files selected for processing (3)
  • crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.html
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.html

@aviraldua93 aviraldua93 force-pushed the feat/html-no-aria-hidden-on-focusable branch from a91f417 to b57ecff Compare March 16, 2026 04:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs (2)

171-180: ⚠️ Potential issue | 🟠 Major

Treat empty/missing contenteditable value as truthy.

At Line 175-179, has_contenteditable_true misses <div contenteditable ...> and <div contenteditable="" ...>, which should both be treated as editable in HTML. This creates false negatives.

Proposed patch
 fn has_contenteditable_true(element: &AnyHtmlElement) -> bool {
     element
         .find_attribute_by_name("contenteditable")
-        .is_some_and(|attr| {
-            attr.value().is_some_and(|value| {
-                let trimmed = value.trim();
-                !trimmed.is_empty() && !trimmed.eq_ignore_ascii_case("false")
-            })
-        })
+        .is_some_and(|attr| match attr.value() {
+            None => true,
+            Some(value) => {
+                let trimmed = value.trim();
+                trimmed.is_empty() || !trimmed.eq_ignore_ascii_case("false")
+            }
+        })
 }
Per HTML spec, what state does contenteditable have when present without a value, or with an empty string value?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs`
around lines 171 - 180, The function has_contenteditable_true incorrectly treats
elements like <div contenteditable> and <div contenteditable=""> as
non-editable; update has_contenteditable_true to treat presence of the
contenteditable attribute with no value or an empty string as truthy per the
HTML spec by returning true when the attribute exists and its value is either
missing or empty, and only return false when the attribute value is explicitly
"false" (case-insensitive); locate has_contenteditable_true and adjust the
attribute value check to consider attr.value() being None or Some("") as true
while still treating "false" as false.

155-164: ⚠️ Potential issue | 🟠 Major

Exclude hidden inputs from native-focusable detection.

Line 157 currently treats all <input> elements as focusable. That includes type="hidden", which should not trigger this rule, so this can raise false positives. Also add a fixture for <input type="hidden" aria-hidden="true"> to lock this down.

Proposed patch
-        if name_matches("button")
-            || name_matches("input")
-            || name_matches("select")
+        if name_matches("button")
+            || name_matches("select")
             || name_matches("textarea")
             || name_matches("details")
             || name_matches("summary")
         {
             return true;
         }
+
+        if name_matches("input") {
+            let is_hidden_input = element
+                .find_attribute_by_name("type")
+                .and_then(|attr| attr.value())
+                .is_some_and(|value| value.trim().eq_ignore_ascii_case("hidden"));
+            return !is_hidden_input;
+        }
In the HTML Standard, is <input type="hidden"> part of sequential keyboard focus navigation (tab order)?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs`
around lines 155 - 164, The current native-focusable check treats all inputs as
focusable (the name_matches("input") branch) which incorrectly includes <input
type="hidden">; update the logic so the input case explicitly excludes inputs
where the type attribute is "hidden" (e.g., check the element's "type" attribute
or add an is_hidden_input helper and use it in the early-return condition), and
add a fixture/test for <input type="hidden" aria-hidden="true"> to verify it no
longer triggers the rule; ensure you update the same function containing the
name_matches("input") check so hidden inputs are not considered focusable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs`:
- Around line 171-180: The function has_contenteditable_true incorrectly treats
elements like <div contenteditable> and <div contenteditable=""> as
non-editable; update has_contenteditable_true to treat presence of the
contenteditable attribute with no value or an empty string as truthy per the
HTML spec by returning true when the attribute exists and its value is either
missing or empty, and only return false when the attribute value is explicitly
"false" (case-insensitive); locate has_contenteditable_true and adjust the
attribute value check to consider attr.value() being None or Some("") as true
while still treating "false" as false.
- Around line 155-164: The current native-focusable check treats all inputs as
focusable (the name_matches("input") branch) which incorrectly includes <input
type="hidden">; update the logic so the input case explicitly excludes inputs
where the type attribute is "hidden" (e.g., check the element's "type" attribute
or add an is_hidden_input helper and use it in the early-return condition), and
add a fixture/test for <input type="hidden" aria-hidden="true"> to verify it no
longer triggers the rule; ensure you update the same function containing the
name_matches("input") check so hidden inputs are not considered focusable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 08de291f-12a3-42f6-85da-cbf9ffc788ea

📥 Commits

Reviewing files that changed from the base of the PR and between a91f417 and b57ecff.

⛔ Files ignored due to path filters (2)
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.html.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (4)
  • .changeset/port-no-aria-hidden-on-focusable-html.md
  • crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.html
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.html
✅ Files skipped from review due to trivial changes (1)
  • .changeset/port-no-aria-hidden-on-focusable-html.md

@aviraldua93 aviraldua93 force-pushed the feat/html-no-aria-hidden-on-focusable branch from b57ecff to 31fb6dc Compare March 16, 2026 04:43
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.html`:
- Around line 9-10: The test snapshot for the noAriaHiddenOnFocusable spec is
out of sync because two new invalid inputs were added (contenteditable elements
with aria-hidden); regenerate and commit the updated snapshot for that fixture:
run the snapshot-update command used by this repo (e.g. the insta snapshot
update flow or the project’s test update command) for the
tests/specs/a11y/noAriaHiddenOnFocusable suite, verify the new diagnostics for
the two added lines are present, and commit the updated invalid.html.snap so the
fixture and expected output match.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6d1b4aee-6ec5-4d0a-b339-dda8ee5672bc

📥 Commits

Reviewing files that changed from the base of the PR and between b57ecff and 31fb6dc.

⛔ Files ignored due to path filters (2)
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.html.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (5)
  • .changeset/port-no-aria-hidden-on-focusable-html.md
  • crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.html
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.html
  • pr-body.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • .changeset/port-no-aria-hidden-on-focusable-html.md
  • crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs

Comment on lines +9 to +10
<div contenteditable aria-hidden="true">editable bare attr</div>
<div contenteditable="" aria-hidden="true">editable empty string</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update the spec snapshot for the new contenteditable cases.

Lines 9–10 add two new invalid inputs, but the existing invalid.html.snap (shown in context) doesn’t include their diagnostics yet. Please regenerate/update the snapshot so the fixture and expected output stay in sync.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.html`
around lines 9 - 10, The test snapshot for the noAriaHiddenOnFocusable spec is
out of sync because two new invalid inputs were added (contenteditable elements
with aria-hidden); regenerate and commit the updated snapshot for that fixture:
run the snapshot-update command used by this repo (e.g. the insta snapshot
update flow or the project’s test update command) for the
tests/specs/a11y/noAriaHiddenOnFocusable suite, verify the new diagnostics for
the two added lines are present, and commit the updated invalid.html.snap so the
fixture and expected output match.

@Netail Netail mentioned this pull request Mar 16, 2026
32 tasks
Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, there are a lot of missing tests:

  • case sensitivity
  • components

"@biomejs/biome": minor
---

# Port `noAriaHiddenOnFocusable` a11y lint rule to HTML
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to our contribution guide on how to write a changeset

Comment on lines +79 to +80
if let Some(tabindex_attr) = element.find_attribute_by_name("tabindex") {
if let Some(tabindex_value) = get_tabindex_value(&tabindex_attr) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use let chain. You didn't use clippy, did you ?

/// Parses the tabindex attribute value as an integer.
fn get_tabindex_value(attribute: &HtmlAttribute) -> Option<i32> {
let value = attribute.value()?;
value.trim().parse::<i32>().ok()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using i32 seems a bit much. Does the JSX rule use the same logic?

value.trim().parse::<i32>().ok()
}

/// Checks if an element is natively focusable or has contenteditable.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring should explain the business logic of a function. Here, you want to explain when an element is focusable

}

/// Checks if an element is natively focusable or has contenteditable.
fn is_focusable_element(element: &AnyHtmlElement, is_html: bool) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the return type to Option<bool> so that you can use the try operator a reduce code branching

if is_html {
element_name.eq_ignore_ascii_case(name)
} else {
element_name.text() == name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You muse use text_trimmed

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 17, 2026

Merging this PR will degrade performance by 23.76%

❌ 1 regressed benchmark
✅ 63 untouched benchmarks
⏩ 152 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
html_analyzer[index_1033418810622582172.html] 442.8 µs 580.7 µs -23.76%

Comparing aviraldua93:feat/html-no-aria-hidden-on-focusable (31fb6dc) with next (8f47f06)

Open in CodSpeed

Footnotes

  1. 152 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Port the noAriaHiddenOnFocusable lint rule from JSX to HTML. The rule
enforces that aria-hidden=true is not set on focusable elements.

Addresses review feedback:
- Use let-chains for tabindex checking (clippy-friendly)
- Return Option<bool> from is_focusable_element with ? operator
- Add business-logic doc comments to all helper functions
- Exclude input type=hidden from focusability (HTML spec)
- Fix contenteditable to match HTML spec enumerated attribute states:
  only true/empty/plaintext-only are editing hosts, invalid values
  map to Inherit state (not an editing host)
- Add case sensitivity tests (BUTTON, INPUT uppercase)
- Add component test (MyButton — not flagged)
- Add area/details/summary test coverage
- Changeset uses patch with example snippet

Part of biomejs#8155.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@aviraldua93 aviraldua93 force-pushed the feat/html-no-aria-hidden-on-focusable branch from 31fb6dc to 1ffe6b4 Compare March 25, 2026 03:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.html (1)

13-13: Consider adding an invalid contenteditable value test case.

Per the HTML spec, invalid values like contenteditable="banana" fall back to the Inherit state (not an editing host). Adding a case like <div contenteditable="banana" aria-hidden="true"> would document this edge case behaviour.

Suggested addition
 <div contenteditable="false" aria-hidden="true">not editable</div>
+<div contenteditable="banana" aria-hidden="true">invalid value inherits</div>
 <BUTTON aria-hidden="true" tabindex="-1"></BUTTON>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.html`
at line 13, Add a test case showing the invalid contenteditable value behavior
by including an element like <div contenteditable="banana"
aria-hidden="true">not editable</div> in the valid.html tests for
noAriaHiddenOnFocusable; this documents that invalid values fall back to Inherit
(not an editing host) so aria-hidden on that element is acceptable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs`:
- Around line 165-173: The natively-focusable check (the block using
name_matches for "button", "select", "textarea", "details", "summary") should
also verify the element is not disabled before returning Some(true); update that
logic (e.g., call element.find_attribute_by_name("disabled").is_none()) so
disabled form controls are treated as unfocusable, and add corresponding tests
(e.g., fixtures with <button disabled aria-hidden="true">) to the valid cases to
cover this behavior for both the Rust rule and the JS equivalent.

---

Nitpick comments:
In
`@crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.html`:
- Line 13: Add a test case showing the invalid contenteditable value behavior by
including an element like <div contenteditable="banana" aria-hidden="true">not
editable</div> in the valid.html tests for noAriaHiddenOnFocusable; this
documents that invalid values fall back to Inherit (not an editing host) so
aria-hidden on that element is acceptable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6126c960-eb87-415d-b914-53e65ab076e4

📥 Commits

Reviewing files that changed from the base of the PR and between 31fb6dc and 1ffe6b4.

📒 Files selected for processing (4)
  • .changeset/port-no-aria-hidden-on-focusable-html.md
  • crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.html
  • crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.html
✅ Files skipped from review due to trivial changes (1)
  • .changeset/port-no-aria-hidden-on-focusable-html.md

Comment on lines +165 to +173
// These elements are always natively focusable
if name_matches("button")
|| name_matches("select")
|| name_matches("textarea")
|| name_matches("details")
|| name_matches("summary")
{
return Some(true);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if the JSX version handles disabled elements
rg -n -A 10 "disabled" crates/biome_js_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs

Repository: biomejs/biome

Length of output: 39


🏁 Script executed:

# First, let's find the correct paths for both HTML and JS rules
fd -t f "no_aria_hidden_on_focusable" crates/

Repository: biomejs/biome

Length of output: 257


🏁 Script executed:

# Let's also check the structure of the analyzers
ls -la crates/ | grep analyze

Repository: biomejs/biome

Length of output: 536


🏁 Script executed:

# Search for how disabled is handled in the HTML rule
rg -n "disabled" crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs

Repository: biomejs/biome

Length of output: 39


🏁 Script executed:

# Read the HTML rule implementation
wc -l crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs

Repository: biomejs/biome

Length of output: 131


🏁 Script executed:

# Read the JS rule implementation
wc -l crates/biome_js_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs

Repository: biomejs/biome

Length of output: 129


🏁 Script executed:

# Check test specs for HTML rule
fd -t f "no_aria_hidden_on_focusable" crates/biome_html_analyze/tests/

Repository: biomejs/biome

Length of output: 39


🏁 Script executed:

# Read the HTML rule implementation
cat -n crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs | head -100

Repository: biomejs/biome

Length of output: 4364


🏁 Script executed:

# Read the JS rule implementation
cat -n crates/biome_js_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs

Repository: biomejs/biome

Length of output: 6722


🏁 Script executed:

# Find test specs with broader search
find crates/biome_html_analyze/tests -type d | grep -i "no_aria\|focusable\|a11y"

Repository: biomejs/biome

Length of output: 1914


🏁 Script executed:

# Read the rest of HTML rule to see the is_focusable_element function and lines 165-173
cat -n crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs | tail -n +100

Repository: biomejs/biome

Length of output: 5374


🏁 Script executed:

# Check HTML test specs
ls -la crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/

Repository: biomejs/biome

Length of output: 289


🏁 Script executed:

# Read HTML test spec files
cat crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.html

Repository: biomejs/biome

Length of output: 799


🏁 Script executed:

cat crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.html

Repository: biomejs/biome

Length of output: 738


🏁 Script executed:

# Check if there's a JS test spec for comparison
ls -la crates/biome_js_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/ 2>/dev/null || echo "No JS test spec found"

Repository: biomejs/biome

Length of output: 424


🏁 Script executed:

# Read JS test specs
cat crates/biome_js_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/valid.jsx

Repository: biomejs/biome

Length of output: 367


🏁 Script executed:

cat crates/biome_js_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/invalid.jsx

Repository: biomejs/biome

Length of output: 284


🏁 Script executed:

# Search for "disabled" in both test directories
rg -n "disabled" crates/biome_html_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/
rg -n "disabled" crates/biome_js_analyze/tests/specs/a11y/noAriaHiddenOnFocusable/

Repository: biomejs/biome

Length of output: 39


Both rules should check the disabled attribute for natively focusable elements.

Per the HTML spec, disabled form controls (<button disabled>, <input disabled>, etc.) are not focusable and therefore shouldn't trigger this lint when paired with aria-hidden="true". Currently, neither the HTML nor the JS rule checks the disabled attribute—the HTML rule at lines 165–173 returns Some(true) for buttons/inputs without verifying they're enabled. Test coverage for disabled elements is also missing in both implementations.

Recommend adding a check: element.find_attribute_by_name("disabled").is_none() before marking these elements as focusable, and adding test cases like <button disabled aria-hidden="true"> to the valid fixtures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs`
around lines 165 - 173, The natively-focusable check (the block using
name_matches for "button", "select", "textarea", "details", "summary") should
also verify the element is not disabled before returning Some(true); update that
logic (e.g., call element.find_attribute_by_name("disabled").is_none()) so
disabled form controls are treated as unfocusable, and add corresponding tests
(e.g., fixtures with <button disabled aria-hidden="true">) to the valid cases to
cover this behavior for both the Rust rule and the JS equivalent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter L-HTML Language: HTML and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants