From f71a6813b7bf9c53b1dce0b5f3a1232f55b1f26a Mon Sep 17 00:00:00 2001 From: Artyom Alekseevich <47069814+FrankFMY@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:13:54 +0100 Subject: [PATCH] fix: treat menu element like ul/ol for a11y role checks (#17638) * treat menu element like ul/ol for a11y role checks The element has the same implicit role (list) as
    and
      , so it should receive the same treatment in a11y checks: - Allow without redundant role warning (CSS list-style:none can remove semantics, role restores them) - Allow with interactive roles like menu, menubar, radiogroup, tablist, tree, treegrid (same exceptions as ul/ol) Fixes #8529 * changeset --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/fix-menu-element-a11y-roles.md | 5 +++++ .../2-analyze/visitors/shared/a11y/constants.js | 1 + .../phases/2-analyze/visitors/shared/a11y/index.js | 2 +- .../input.svelte | 7 +++++++ .../samples/a11y-no-redundant-roles/input.svelte | 4 ++-- .../samples/a11y-no-redundant-roles/warnings.json | 12 ------------ 6 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 .changeset/fix-menu-element-a11y-roles.md diff --git a/.changeset/fix-menu-element-a11y-roles.md b/.changeset/fix-menu-element-a11y-roles.md new file mode 100644 index 0000000000..75cfe8d310 --- /dev/null +++ b/.changeset/fix-menu-element-a11y-roles.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: treat `` like `
        `/`
          ` for a11y role checks diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/constants.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/constants.js index 684ea92094..49099c2cb1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/constants.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/constants.js @@ -174,6 +174,7 @@ export const input_type_to_implicit_role = new Map([ export const a11y_non_interactive_element_to_interactive_role_exceptions = { ul: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'], ol: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'], + menu: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'], li: ['menuitem', 'option', 'row', 'tab', 'treeitem'], table: ['grid'], td: ['gridcell'], diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js index f5f4982ed2..45de8b10a1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y/index.js @@ -167,7 +167,7 @@ export function check_element(node, context) { if ( current_role === get_implicit_role(node.name, attribute_map) && //
            is ok because CSS list-style:none removes the semantics and this is a way to bring them back - !['ul', 'ol', 'li'].includes(node.name) && + !['ul', 'ol', 'li', 'menu'].includes(node.name) && // is ok because without href the a tag doesn't have a role of link !(node.name === 'a' && !attribute_map.has('href')) ) { diff --git a/packages/svelte/tests/validator/samples/a11y-no-noninteractive-element-to-interactive-role/input.svelte b/packages/svelte/tests/validator/samples/a11y-no-noninteractive-element-to-interactive-role/input.svelte index e5db8719b0..edfa3eeada 100644 --- a/packages/svelte/tests/validator/samples/a11y-no-noninteractive-element-to-interactive-role/input.svelte +++ b/packages/svelte/tests/validator/samples/a11y-no-noninteractive-element-to-interactive-role/input.svelte @@ -69,6 +69,13 @@
          • + + + + + + +
            diff --git a/packages/svelte/tests/validator/samples/a11y-no-redundant-roles/input.svelte b/packages/svelte/tests/validator/samples/a11y-no-redundant-roles/input.svelte index 2ccd765aa2..9e06ebe420 100644 --- a/packages/svelte/tests/validator/samples/a11y-no-redundant-roles/input.svelte +++ b/packages/svelte/tests/validator/samples/a11y-no-redundant-roles/input.svelte @@ -22,7 +22,7 @@
            - +
            @@ -44,5 +44,5 @@
            - + diff --git a/packages/svelte/tests/validator/samples/a11y-no-redundant-roles/warnings.json b/packages/svelte/tests/validator/samples/a11y-no-redundant-roles/warnings.json index 861f000aef..e041037d71 100644 --- a/packages/svelte/tests/validator/samples/a11y-no-redundant-roles/warnings.json +++ b/packages/svelte/tests/validator/samples/a11y-no-redundant-roles/warnings.json @@ -275,18 +275,6 @@ "line": 24 } }, - { - "code": "a11y_no_redundant_roles", - "end": { - "column": 17, - "line": 25 - }, - "message": "Redundant role 'list'", - "start": { - "column": 6, - "line": 25 - } - }, { "code": "a11y_no_redundant_roles", "end": {