Skip to content

feat(biome_html_analyze): port useAriaActivedescendantWithTabindex to HTML#9617

Merged
dyc3 merged 6 commits intobiomejs:nextfrom
faizkhairi:feat/html-use-aria-activedescendant-with-tabindex
Mar 27, 2026
Merged

feat(biome_html_analyze): port useAriaActivedescendantWithTabindex to HTML#9617
dyc3 merged 6 commits intobiomejs:nextfrom
faizkhairi:feat/html-use-aria-activedescendant-with-tabindex

Conversation

@faizkhairi
Copy link
Copy Markdown

Summary

Port the existing JSX useAriaActivedescendantWithTabindex a11y rule to work with HTML elements, as part of the umbrella issue #8155.

Description

The rule checks that non-interactive HTML elements with aria-activedescendant also have a tabindex attribute to ensure they are keyboard-accessible.

Logic

  1. Find elements with aria-activedescendant attribute
  2. Skip natively interactive elements (input, button, select, textarea) — they are focusable by default
  3. Skip <a> elements with href — also natively focusable
  4. Check if tabindex attribute is present — if not, report a diagnostic

Test cases

Valid (no diagnostic):

  • <div aria-activedescendant="id" tabindex="0"> — has tabindex
  • <input aria-activedescendant="id"> — natively interactive
  • <button aria-activedescendant="id"> — natively interactive
  • <a href="#" aria-activedescendant="id"> — anchor with href

Invalid (diagnostic):

  • <div aria-activedescendant="id"> — non-interactive, no tabindex
  • <span aria-activedescendant="id"> — non-interactive, no tabindex
  • <a aria-activedescendant="id"> — anchor without href is non-interactive

Related

  • Part of ☂️ Port a11y rules to HTML #8155
  • Mirrors the JSX rule at crates/biome_js_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs
  • Source: eslint-plugin-jsx-a11y/aria-activedescendant-has-tabindex

AI Disclosure

This contribution used AI assistance (Claude Code) for codebase exploration and implementation.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 25, 2026

🦋 Changeset detected

Latest commit: 1734a54

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

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

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an HTML accessibility lint rule UseAriaActivedescendantWithTabindex (recommended, Severity::Error, version: "next") that runs on AnyHtmlElement nodes. The rule triggers when an element has aria-activedescendant and suppresses diagnostics for inherently tabbable interactive elements (button, input, select, textarea), for a elements with an href, or for any element already having a tabindex. Matching is case-insensitive for native .html sources and case-sensitive for component frameworks. If none apply, it emits a diagnostic instructing to add a tabindex (value ≥ -1). Adds valid/invalid test fixtures and a changeset.

Suggested reviewers

  • dyc3
  • ematipico
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the main change: porting an accessibility lint rule to HTML.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the rule's purpose, logic, test cases, and context.
Linked Issues check ✅ Passed The PR clearly references umbrella issue #8155 and mirrors the existing JSX rule as documented.
Out of Scope Changes check ✅ Passed All changes are strictly within scope: new lint rule for HTML, test fixtures, and a changelog entry.

✏️ 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
Copy Markdown
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.

🧹 Nitpick comments (1)
crates/biome_html_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs (1)

68-75: Minor: redundant element name extraction.

is_anchor_with_href re-extracts the element name, but the caller (run) has already computed it. You could pass the element name as a parameter to avoid the duplication.

♻️ Proposed refactor
-fn is_anchor_with_href(element: &AnyHtmlElement) -> bool {
-    let name = element.name().ok();
-    let name_text = name.as_ref().and_then(|n| n.value_token().ok());
-    let Some(name_str) = name_text.as_ref().map(|t| t.text_trimmed()) else {
-        return false;
-    };
-    name_str == "a" && element.find_attribute_by_name("href").is_some()
+fn is_anchor_with_href(element: &AnyHtmlElement, element_name: &str) -> bool {
+    element_name == "a" && element.find_attribute_by_name("href").is_some()
 }

Then update the call site at line 102:

if is_anchor_with_href(element, element_name) {
🤖 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/use_aria_activedescendant_with_tabindex.rs`
around lines 68 - 75, The helper is_anchor_with_href currently re-reads the
element name redundantly; modify its signature to accept the precomputed
element_name (e.g., change to is_anchor_with_href(element: &AnyHtmlElement,
element_name: &str)) and update the caller in run to pass element_name (replace
is_anchor_with_href(element) with is_anchor_with_href(element, element_name));
inside is_anchor_with_href, use the passed element_name to check equality to "a"
and keep the existing href attribute presence check via
element.find_attribute_by_name("href").is_some().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@crates/biome_html_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs`:
- Around line 68-75: The helper is_anchor_with_href currently re-reads the
element name redundantly; modify its signature to accept the precomputed
element_name (e.g., change to is_anchor_with_href(element: &AnyHtmlElement,
element_name: &str)) and update the caller in run to pass element_name (replace
is_anchor_with_href(element) with is_anchor_with_href(element, element_name));
inside is_anchor_with_href, use the passed element_name to check equality to "a"
and keep the existing href attribute presence check via
element.find_attribute_by_name("href").is_some().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e7f9f1ef-f507-4d28-a83e-41567b8f0537

📥 Commits

Reviewing files that changed from the base of the PR and between 4620d22 and 9ecf5c6.

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

Comment on lines +57 to +70
/// HTML elements that are natively interactive (focusable without tabindex).
const INTERACTIVE_ELEMENTS: &[&str] = &[
"button", "input", "select", "textarea",
];

/// Check if the HTML element is natively interactive.
fn is_interactive_element(element_name: &str) -> bool {
INTERACTIVE_ELEMENTS.contains(&element_name)
}

/// Check if the element is an anchor with an href (which is natively focusable).
fn is_anchor_with_href(element: &AnyHtmlElement, element_name: &str) -> bool {
element_name == "a" && element.find_attribute_by_name("href").is_some()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: move all helpers below the impl Rule block

Comment on lines +58 to +65
const INTERACTIVE_ELEMENTS: &[&str] = &[
"button", "input", "select", "textarea",
];

/// Check if the HTML element is natively interactive.
fn is_interactive_element(element_name: &str) -> bool {
INTERACTIVE_ELEMENTS.contains(&element_name)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this check must be case insensitive in HTML files, but case sensitive in HTML-ish template languages (vue, svelte, astro)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@coderabbitai add this to your learnings: When checking element names, the check must be case insensitive in .html files, but case sensitive in .astro, .vue and .svelte files because of how components work in those languages

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@dyc3, noted! I've added this to my long-term memory. 🐇

(ノ◕ヮ◕)ノ*:・゚✧


✏️ Learnings added
Learnt from: dyc3
Repo: biomejs/biome PR: 9617
File: crates/biome_html_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs:58-65
Timestamp: 2026-03-25T13:49:08.724Z
Learning: When checking HTML element names in biome_html_analyze lint rules (e.g., checking against lists like INTERACTIVE_ELEMENTS), the comparison must be **case-insensitive** for `.html` files, but **case-sensitive** for `.astro`, `.vue`, and `.svelte` files. In template languages like Astro, Vue, and Svelte, PascalCase names denote custom components (not native HTML elements), so a case-sensitive match is required to correctly distinguish them from lowercase native elements.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: ematipico
Repo: biomejs/biome PR: 9369
File: crates/biome_html_analyze/tests/specs/a11y/useAnchorContent/vue/valid.vue:20-22
Timestamp: 2026-03-08T08:18:33.839Z
Learning: In `crates/biome_html_analyze/src/lint/a11y/use_anchor_content.rs` (biomejs/biome), custom components (PascalCase elements) inside `<a>` tags are intentionally treated as potentially accessible content to avoid false positives. The linter cannot statically determine what a custom component renders, so it must not emit a diagnostic for anchors whose only children are custom components. The `valid` fixture files for non-Astro frameworks (e.g., vue/valid.vue, svelte/valid.svelte) correctly include cases like `<a><Image alt="description" /></a>` and `<a><CustomIcon /></a>` as valid to document this behavior.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Mark code blocks in rule documentation as 'expect_diagnostic' when showing invalid examples, and each block must emit exactly one diagnostic

Learnt from: ematipico
Repo: biomejs/biome PR: 8537
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:167-210
Timestamp: 2025-12-22T09:26:56.943Z
Learning: When defining lint rules (declare_lint_rule!), only specify fix_kind if the rule implements an action(...) function. Rules that only emit diagnostics without a code fix should omit fix_kind. This applies to all Rust lint rule definitions under crates/.../src/lint (e.g., crates/biome_js_analyze/src/lint/...).

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Valid code blocks in rule documentation must not report any diagnostics, and can be marked with 'ignore' to skip automatic validation if needed

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Implement custom syntax tree visitors using the 'Visitor' trait and 'Queryable' trait when rules need to deeply inspect child nodes before deciding to emit signals

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Rule names should use 'use<Concept>' prefix when mandating a single concept (e.g., 'useValidLang' to mandate valid lang attribute values)

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Use 'Semantic<T>' query type instead of 'Ast<T>' to access the semantic model for analyzing variable bindings and references in lint rules

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : For rules ported from other linters, use the 'sources' field in 'declare_lint_rule!' to specify 'RuleSource::Eslint(rule_name)' with either '.same()' or '.inspired()'

Learnt from: dyc3
Repo: biomejs/biome PR: 8639
File: crates/biome_js_analyze/src/lint/nursery/no_excessive_lines_per_file.rs:101-108
Timestamp: 2025-12-31T15:35:41.261Z
Learning: In crates/biome_analyze/**/*analyze/src/lint/nursery/**/*.rs, the `issue_number` field in `declare_lint_rule!` macro is optional and the vast majority of nursery rules do not need it. Do not recommend adding `issue_number` unless there's a specific reason.

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Use rule domains (e.g., 'RuleDomain::Test') to categorize rules and allow users to opt-in/opt-out based on specific concepts like testing frameworks

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Set 'version: "next"' in 'declare_lint_rule!' macro to indicate the rule is unreleased and allow flexibility for what version it will be released in

Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2026-03-16T13:19:14.790Z
Learning: Applies to **/crates/biome_analyze/**/*.rs : Update the `version` metadata field in linter rules from 'next' to the new version number when releasing a new minor or major version

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Use the 'issue_number' field in the 'declare_lint_rule!' macro to mark incomplete rules as work in progress, which adds a footnote link to the GitHub issue

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Add a 'deprecated' field to 'declare_lint_rule!' macro when deprecating a rule to communicate the reason for deprecation (e.g., suggesting an alternative rule)

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Set rule severity to 'error' for rules in 'correctness', 'security', and 'a11y' groups; use 'warn' or 'info' for other groups based on rule type

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Document rule options in a '## Options' section with h3 headers for each option, describing what it does, its default value, and providing code examples

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2026-03-21T17:22:03.949Z
Learning: Applies to crates/biome_analyze/**/lint/**/*.rs : Rules with 'recommended: true' and specific domains are only enabled when users enable those domains, while recommended rules without domains are always enabled

Learnt from: dyc3
Repo: biomejs/biome PR: 8901
File: crates/biome_analyze_macros/src/group_macro.rs:9-9
Timestamp: 2026-01-29T22:10:30.888Z
Learning: Do not import xtask inside biome crates. In the biomeps repository, ensure that biome crates (e.g., crates/biome_*/...) do not have imports like use xtask_glue::... or other xtask-related crates. The correct dependency direction is for xtask to depend on and import biome crates, not the reverse, since xtask contains heavier utilities (e.g., git2) that can inflate build times when included in biome crates. When reviewing, verify dependency declarations (Cargo.toml) and any use statements to confirm xtask crates are not pulled into biome crates; prefer having xtask depend on biome crates and export needed functionality through stable interfaces.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these are missing snapshot files, run cargo test

@dyc3 dyc3 added the M-Likely Agent Meta: this was likely an automated PR without a human in the loop label Mar 25, 2026
@faizkhairi
Copy link
Copy Markdown
Author

thanks for the input, @dyc3 . i will perform the fix regarding case insensitives and the remaining missing, then run cargo test. Understandable regarding the label 😆

@Netail Netail mentioned this pull request Mar 25, 2026
32 tasks
"@biomejs/biome": minor
---

Port `useAriaActivedescendantWithTabindex` a11y rule to HTML. The rule enforces that non-interactive HTML elements with `aria-activedescendant` also have a `tabindex` attribute.
Copy link
Copy Markdown
Contributor

@dyc3 dyc3 Mar 25, 2026

Choose a reason for hiding this comment

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

use past tense, link to the rule's docs page

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

use past tense, link to the rule's docs page

understood. okay, will do 👌🏼

@github-actions github-actions Bot added A-Linter Area: linter L-HTML Language: HTML and super languages labels Mar 25, 2026
@faizkhairi
Copy link
Copy Markdown
Author

Update the changeset now uses past tense ("Ported") with a docs link, matching the exact format from other biome changesets like drizzle-update-rule.md

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 25, 2026

Merging this PR will not alter performance

✅ 15 untouched benchmarks
⏩ 196 skipped benchmarks1


Comparing faizkhairi:feat/html-use-aria-activedescendant-with-tabindex (09efd02) with next (a5c7dd9)

Open in CodSpeed

Footnotes

  1. 196 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.

@faizkhairi faizkhairi force-pushed the feat/html-use-aria-activedescendant-with-tabindex branch from d6ee7c1 to 4685753 Compare March 25, 2026 16:46
@faizkhairi
Copy link
Copy Markdown
Author

Merging this PR will degrade performance by 18.54%

❌ 4 regressed benchmarks ✅ 63 untouched benchmarks ⏩ 152 skipped benchmarks1

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

Performance Changes

Benchmark BASE HEAD Efficiency
html_analyzer[real/wikipedia-fr-Guerre_de_Canudos.html] 422.7 ms 466.9 ms -9.48%
html_analyzer[real/wikipedia-JavaScript.html] 162 ms 192.7 ms -15.94%
html_analyzer[index_1033418810622582172.html] 442.8 µs 472.6 µs -6.3%
html_analyzer[real/wikipedia-Unix.html] 138.1 ms 169.5 ms -18.54%
Comparing faizkhairi:feat/html-use-aria-activedescendant-with-tabindex (2599293) with main (4620d22)

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.

The performance regression flagged here is expected. This is a new lint rule added to the HTML analyzer, so every element in the document incurs the rule dispatch overhead.... (even though the rule short-circuits immediately when aria-activedescendant is absent).

For reference, PR #9582 (port useKeyWithMouseEvents to HTML) showed a similar regression of 24.66% on the same benchmarks and was merged without issue.

Branch has been update and rebased on latest main.

Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
crates/biome_html_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs (1)

109-126: Consider rephrasing the diagnostic message.

The current message "Enforce elements with aria-activedescendant are tabbable." reads like a rule description rather than an error message. Something like "This element has aria-activedescendant but is not tabbable." would be clearer.

💡 Suggested rewording
             RuleDiagnostic::new(
                 rule_category!(),
                 element.range(),
-                "Enforce elements with aria-activedescendant are tabbable.",
+                "This element has aria-activedescendant but is not tabbable.",
             )
🤖 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/use_aria_activedescendant_with_tabindex.rs`
around lines 109 - 126, The diagnostic text in diagnostic() (inside
RuleDiagnostic::new(...) using element.range()) is phrased like a rule title
instead of an actionable error; update the message string from "Enforce elements
with aria-activedescendant are tabbable." to a clearer error such as "This
element has aria-activedescendant but is not tabbable." so the diagnostic reads
as a specific problem for the offending element while keeping the existing notes
and markup unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@crates/biome_html_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs`:
- Around line 109-126: The diagnostic text in diagnostic() (inside
RuleDiagnostic::new(...) using element.range()) is phrased like a rule title
instead of an actionable error; update the message string from "Enforce elements
with aria-activedescendant are tabbable." to a clearer error such as "This
element has aria-activedescendant but is not tabbable." so the diagnostic reads
as a specific problem for the offending element while keeping the existing notes
and markup unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1be0d844-d0c7-404b-9abe-93808bf64d5f

📥 Commits

Reviewing files that changed from the base of the PR and between 2599293 and 4685753.

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

@faizkhairi
Copy link
Copy Markdown
Author

faizkhairi commented Mar 25, 2026

🧹 Nitpick comments (1)

crates/biome_html_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs (1)> 109-126: Consider rephrasing the diagnostic message.

The current message "Enforce elements with aria-activedescendant are tabbable." reads like a rule description rather than an error message. Something like "This element has aria-activedescendant but is not tabbable." would be clearer.

💡 Suggested rewording

             RuleDiagnostic::new(
                 rule_category!(),
                 element.range(),
-                "Enforce elements with aria-activedescendant are tabbable.",
+                "This element has aria-activedescendant but is not tabbable.",
             )

🤖 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/use_aria_activedescendant_with_tabindex.rs`
around lines 109 - 126, The diagnostic text in diagnostic() (inside
RuleDiagnostic::new(...) using element.range()) is phrased like a rule title
instead of an actionable error; update the message string from "Enforce elements
with aria-activedescendant are tabbable." to a clearer error such as "This
element has aria-activedescendant but is not tabbable." so the diagnostic reads
as a specific problem for the offending element while keeping the existing notes
and markup unchanged.

🤖 Prompt for all review comments with AI agents

Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@crates/biome_html_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs`:
- Around line 109-126: The diagnostic text in diagnostic() (inside
RuleDiagnostic::new(...) using element.range()) is phrased like a rule title
instead of an actionable error; update the message string from "Enforce elements
with aria-activedescendant are tabbable." to a clearer error such as "This
element has aria-activedescendant but is not tabbable." so the diagnostic reads
as a specific problem for the offending element while keeping the existing notes
and markup unchanged.

ℹ️ Review info

Hmmm...

Re: CodeRabbit's diagnostic message suggestion - keeping the current wording intentionally. It matches the original JSX rule (use_aria_activedescendant_with_tabindex) verbatim, so users see the same message whether the rule fires in JSX or HTML.

If the wording should be improved, it'd make more sense as a separate change across both.

Copy link
Copy Markdown
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

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

one more thing, this needs to point to next and not main

@faizkhairi faizkhairi force-pushed the feat/html-use-aria-activedescendant-with-tabindex branch from 4685753 to c3b556a Compare March 25, 2026 18:34
@faizkhairi faizkhairi changed the base branch from main to next March 25, 2026 18:35
@faizkhairi
Copy link
Copy Markdown
Author

Rebased onto next and updated the PR base branch. Thanks for the catch.

Copy link
Copy Markdown
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.

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

1-10: Good coverage of valid scenarios.

The test cases thoroughly cover all conditions that should suppress diagnostics: elements with tabindex, natively interactive elements, and anchors with href.

Consider adding framework-specific fixtures (e.g., vue/valid.vue, svelte/valid.svelte, astro/valid.astro) to verify the case-sensitive behaviour with PascalCase custom components, similar to other HTML a11y rules like useAnchorContent. This ensures <Div aria-activedescendant="id"></Div> (custom component) isn't incorrectly flagged. Based on learnings: "In component-based frameworks, PascalCase typically denotes custom components, while lowercase names denote native HTML elements."

🤖 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/useAriaActivedescendantWithTabindex/valid.html`
around lines 1 - 10, Add framework-specific valid fixtures for the
useAriaActivedescendantWithTabindex rule to cover PascalCase custom components
so they are not flagged: create files like vue/valid.vue, svelte/valid.svelte,
and astro/valid.astro containing examples such as <Div
aria-activedescendant="id"></Div> (PascalCase custom component) and ensure the
test runner treats these as framework components and asserts no diagnostics,
mirroring the existing checks in
crates/biome_html_analyze/tests/specs/a11y/useAriaActivedescendantWithTabindex/valid.html
for lowercase/native elements and tabindex cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@crates/biome_html_analyze/tests/specs/a11y/useAriaActivedescendantWithTabindex/valid.html`:
- Around line 1-10: Add framework-specific valid fixtures for the
useAriaActivedescendantWithTabindex rule to cover PascalCase custom components
so they are not flagged: create files like vue/valid.vue, svelte/valid.svelte,
and astro/valid.astro containing examples such as <Div
aria-activedescendant="id"></Div> (PascalCase custom component) and ensure the
test runner treats these as framework components and asserts no diagnostics,
mirroring the existing checks in
crates/biome_html_analyze/tests/specs/a11y/useAriaActivedescendantWithTabindex/valid.html
for lowercase/native elements and tabindex cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4623d739-c176-4ba6-ac73-f55cf1e618f7

📥 Commits

Reviewing files that changed from the base of the PR and between 4685753 and 831e2cb.

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

@faizkhairi faizkhairi force-pushed the feat/html-use-aria-activedescendant-with-tabindex branch from 831e2cb to b0a54fa Compare March 26, 2026 23:08
@github-actions github-actions Bot added the L-JSON Language: JSON and super languages label Mar 26, 2026
@faizkhairi
Copy link
Copy Markdown
Author

Rebased and fixed clippy lint (replaced if-is_none pattern with ? operator). Also added missing project_layout field in compat_sortpkg_tests.

@faizkhairi
Copy link
Copy Markdown
Author

faizkhairi commented Mar 26, 2026

Rebased and fixed clippy lint (replaced if-is_none pattern with ? operator). Also added missing project_layout field in compat_sortpkg_tests.

@dyc3 Hi Carson! Sorry to bother you, this PR might be overlooked due to being pushed down with newer PRs. I am here just a little bump and inform that I made a bonus fix pre-existing issue (that cause CI failing) regarding the ones mentioned in this quoted reply. Thankss!

@dyc3
Copy link
Copy Markdown
Contributor

dyc3 commented Mar 27, 2026

at a glance, the ci failures don't look related to your changes. can you verify?

@faizkhairi
Copy link
Copy Markdown
Author

faizkhairi commented Mar 27, 2026

at a glance, the ci failures don't look related to your changes. can you verify?

Actually, the failures are not related to my changes.

All 4 failing jobs (Lint, Check Dependencies, Test, Bench) trace to the same root cause: a type inference error in biome_html_analyze/benches/html_analyzer.rs:160 (error[E0282]).

  • This benchmark was already failing on the next branch since at least March 21.
  • My PR only adds rule files under crates/biome_html_analyze/src/lint/a11y/ and doesn't touch the benchmark..
  • these failures are were pre-existing.. I just can't help myself to address it and to make CI looks green 😅 because it is a simple fix. My apologies 🙏🏼

@dyc3
Copy link
Copy Markdown
Contributor

dyc3 commented Mar 27, 2026

Ah, no worries. I'll look into it

@dyc3 dyc3 self-assigned this Mar 27, 2026
… HTML

Port the existing JSX a11y rule to work with HTML elements.
Checks that non-interactive elements with aria-activedescendant
also have a tabindex attribute to ensure they are tabbable.

Interactive elements (input, button, select, textarea) and
anchor elements with href are excluded as they are natively focusable.

Part of biomejs#8155
faizkhairi and others added 5 commits March 27, 2026 17:56
Address CodeRabbit review: remove redundant name extraction in
is_anchor_with_href by accepting element_name as parameter.
Add changeset for the new HTML a11y rule.
- Make element name checks case-insensitive for .html files using
  eq_ignore_ascii_case, while keeping case-sensitive checks for
  component frameworks (Vue, Svelte, Astro) per biome conventions
- Move helper functions below impl Rule block
- Add snapshot files generated by cargo test
- Add :::note documentation about case sensitivity behavior
- Fix element name extraction to use AnyHtmlElement::name() API
- Replace if-is_none-return-None pattern with ? operator (clippy suggestion)
- Add missing project_layout field to JsonAnalyzeServices in test
@dyc3 dyc3 force-pushed the feat/html-use-aria-activedescendant-with-tabindex branch from 09efd02 to 1734a54 Compare March 27, 2026 21:56
@dyc3 dyc3 merged commit dcb99ef into biomejs:next Mar 27, 2026
3 checks passed
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 L-JSON Language: JSON and super languages M-Likely Agent Meta: this was likely an automated PR without a human in the loop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants