diff --git a/packages/main/cypress/specs/List.cy.tsx b/packages/main/cypress/specs/List.cy.tsx
index 0e3acd85315d..4a45f6b35892 100644
--- a/packages/main/cypress/specs/List.cy.tsx
+++ b/packages/main/cypress/specs/List.cy.tsx
@@ -2796,4 +2796,95 @@ describe("List sticky header", () => {
});
});
});
+});
+
+describe("List - ListItem accessible role inheritance", () => {
+ it("list items inherit 'menuitem' role when ui5-list has accessible-role='Menu'", () => {
+ cy.mount(
+
+ Item 1
+ Item 2
+
+ );
+
+ cy.get("#item1")
+ .shadow()
+ .find("li")
+ .should("have.attr", "role", "menuitem");
+
+ cy.get("#item2")
+ .shadow()
+ .find("li")
+ .should("have.attr", "role", "menuitem");
+ });
+
+ it("list items inherit 'option' role when ui5-list has accessible-role='ListBox'", () => {
+ cy.mount(
+
+ Item 1
+ Item 2
+
+ );
+
+ cy.get("#item1")
+ .shadow()
+ .find("li")
+ .should("have.attr", "role", "option");
+
+ cy.get("#item2")
+ .shadow()
+ .find("li")
+ .should("have.attr", "role", "option");
+ });
+
+ it("list items keep 'listitem' role when ui5-list has default accessible-role='List'", () => {
+ cy.mount(
+
+ Item 1
+
+ );
+
+ cy.get("#item1")
+ .shadow()
+ .find("li")
+ .should("have.attr", "role", "listitem");
+ });
+
+ it("explicit accessible-role on ui5-li takes precedence over inherited role from ui5-list", () => {
+ cy.mount(
+
+ Item 1
+ Item 2
+
+ );
+
+ cy.get("#explicit")
+ .shadow()
+ .find("li")
+ .should("have.attr", "role", "treeitem");
+
+ cy.get("#inherited")
+ .shadow()
+ .find("li")
+ .should("have.attr", "role", "menuitem");
+ });
+
+ it("list items can have an explicit accessible-role set without a parent ui5-list role", () => {
+ cy.mount(
+
+ Item 1
+ Item 2
+
+ );
+
+ cy.get("#item1")
+ .shadow()
+ .find("li")
+ .should("have.attr", "role", "menuitem");
+
+ cy.get("#item2")
+ .shadow()
+ .find("li")
+ .should("have.attr", "role", "listitem");
+ });
});
\ No newline at end of file
diff --git a/packages/main/src/List.ts b/packages/main/src/List.ts
index 4ce0a29f3332..4a116f63ed22 100644
--- a/packages/main/src/List.ts
+++ b/packages/main/src/List.ts
@@ -80,6 +80,13 @@ const INFINITE_SCROLL_DEBOUNCE_RATE = 250; // ms
const PAGE_UP_DOWN_SIZE = 10;
+// Maps the List's accessible-role to the expected child item ARIA role (lowercase)
+const LIST_ACCESSIBLE_ROLE_TO_ITEM_ROLE: Partial> = {
+ Menu: "menuitem",
+ Tree: "treeitem",
+ ListBox: "option",
+};
+
// ListItemBase-based events
type ListItemFocusEventDetail = {
item: ListItemBase,
@@ -843,6 +850,7 @@ class List extends UI5Element {
prepareListItems() {
const slottedItems = this.getItemsForProcessing();
+ const inheritedItemRole = LIST_ACCESSIBLE_ROLE_TO_ITEM_ROLE[this.accessibleRole];
slottedItems.forEach((item, key) => {
const isLastChild = key === slottedItems.length - 1;
@@ -851,6 +859,7 @@ class List extends UI5Element {
if (item.hasConfigurableMode) {
(item as ListItem)._selectionMode = this.selectionMode;
+ (item as ListItem)._inheritedAccessibleRole = inheritedItemRole;
}
item.hasBorder = showBottomBorder;
diff --git a/packages/main/src/ListItem.ts b/packages/main/src/ListItem.ts
index f2d64f0227e1..eb763f8b39f4 100644
--- a/packages/main/src/ListItem.ts
+++ b/packages/main/src/ListItem.ts
@@ -180,17 +180,23 @@ abstract class ListItem extends ListItemBase {
/**
* Used to define the role of the list item.
- * @private
+ *
+ * **Note:** If not set, the role is automatically inherited from the parent `ui5-list` based on its `accessible-role` property
+ * (e.g. `Menu` -> `MenuItem`, `Tree` -> `TreeItem`, `ListBox` -> `Option`).
+ * An explicitly set `accessible-role` on the list item takes precedence over the inherited role.
* @default "ListItem"
+ * @public
* @since 1.3.0
- *
*/
@property()
- accessibleRole: `${ListItemAccessibleRole}` = "ListItem";
+ accessibleRole?: `${ListItemAccessibleRole}`;
@property()
_forcedAccessibleRole?: string;
+ @property({ noAttribute: true })
+ _inheritedAccessibleRole?: string;
+
@property()
_selectionMode: `${ListSelectionMode}` = "None";
@@ -436,7 +442,13 @@ abstract class ListItem extends ListItemBase {
}
get listItemAccessibleRole() {
- return (this._forcedAccessibleRole || this.accessibleRole.toLowerCase()) as AriaRole | undefined;
+ if (this._forcedAccessibleRole) {
+ return this._forcedAccessibleRole as AriaRole;
+ }
+ if (this.accessibleRole) {
+ return this.accessibleRole.toLowerCase() as AriaRole;
+ }
+ return (this._inheritedAccessibleRole || "listitem") as AriaRole;
}
get ariaSelectedText() {
diff --git a/packages/main/test/pages/List_accessible_role.html b/packages/main/test/pages/List_accessible_role.html
new file mode 100644
index 000000000000..9320e3169337
--- /dev/null
+++ b/packages/main/test/pages/List_accessible_role.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+ ui5-list / ui5-li - accessible-role inheritance
+
+
+
+
+
+ List accessible-role ? child role inheritance
+
+
+
+
+ Default: accessible-role="List" (default) ? children get role="listitem"
+
+ Item 1
+ Item 2
+ Item 3
+
+
+
+
+
+ accessible-role="Menu" ? children inherit role="menuitem"
+
+
+
+
+
+ accessible-role="ListBox" ? children inherit role="option"
+
+ Option A
+ Option B
+ Option C
+
+
+
+
+
+ accessible-role="Tree" ? children inherit role="treeitem"
+
+ Node 1
+ Node 2
+ Node 3
+
+
+
+
+
+ Explicit accessible-role on ui5-li overrides inherited role from ui5-list
+
+
+
+
+
+ Explicit accessible-role="MenuItem" set directly on ui5-li (no list role)
+
+ Explicit menuitem
+ Default listitem
+ Default listitem
+
+
+
+
+
diff --git a/packages/website/docs/_components_pages/main/List/List.mdx b/packages/website/docs/_components_pages/main/List/List.mdx
index a203bc9f84c4..cbacf726b022 100644
--- a/packages/website/docs/_components_pages/main/List/List.mdx
+++ b/packages/website/docs/_components_pages/main/List/List.mdx
@@ -14,6 +14,7 @@ import DragAndDrop from "../../../_samples/main/List/DragAndDrop/DragAndDrop.md"
import MultipleDrag from "../../../_samples/main/List/MultipleDrag/MultipleDrag.md";
import WrappingBehavior from "../../../_samples/main/List/WrappingBehavior/WrappingBehavior.md";
import StickyHeader from "../../../_samples/main/List/StickyHeader/StickyHeader.md";
+import AccessibleRole from "../../../_samples/main/List/AccessibleRole/AccessibleRole.md";
<%COMPONENT_OVERVIEW%>
@@ -86,4 +87,12 @@ The `` is intentionally designed as a generic container to provid
### Sticky Header
Use the stickyHeader property to keep the header visible during scrolling.
-
\ No newline at end of file
+
+
+### Accessible Role
+Use the accessibleRole property on `ui5-list` to set the ARIA role of the list element.
+Child `ui5-li` items automatically inherit the matching ARIA role - `menuitem` for `Menu`, `option` for `ListBox`, and `treeitem` for `Tree` - so you do not need to set it manually on every item.
+
+Setting an explicit `accessible-role` attribute on an individual `ui5-li` takes precedence over the role inherited from the parent list.
+
+
\ No newline at end of file
diff --git a/packages/website/docs/_samples/main/List/AccessibleRole/AccessibleRole.md b/packages/website/docs/_samples/main/List/AccessibleRole/AccessibleRole.md
new file mode 100644
index 000000000000..0c062a836e84
--- /dev/null
+++ b/packages/website/docs/_samples/main/List/AccessibleRole/AccessibleRole.md
@@ -0,0 +1,5 @@
+import html from '!!raw-loader!./sample.html';
+import js from '!!raw-loader!./main.js';
+import react from '!!raw-loader!./sample.tsx';
+
+
diff --git a/packages/website/docs/_samples/main/List/AccessibleRole/main.js b/packages/website/docs/_samples/main/List/AccessibleRole/main.js
new file mode 100644
index 000000000000..5a1af4371d9d
--- /dev/null
+++ b/packages/website/docs/_samples/main/List/AccessibleRole/main.js
@@ -0,0 +1,7 @@
+import "@ui5/webcomponents/dist/List.js";
+import "@ui5/webcomponents/dist/ListItemStandard.js";
+
+import "@ui5/webcomponents-icons/dist/create.js";
+import "@ui5/webcomponents-icons/dist/save.js";
+import "@ui5/webcomponents-icons/dist/delete.js";
+import "@ui5/webcomponents-icons/dist/filter.js";
diff --git a/packages/website/docs/_samples/main/List/AccessibleRole/sample.html b/packages/website/docs/_samples/main/List/AccessibleRole/sample.html
new file mode 100644
index 000000000000..73e640261217
--- /dev/null
+++ b/packages/website/docs/_samples/main/List/AccessibleRole/sample.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+ Sample
+
+
+
+
+
+
+
+ New Document
+ Save
+ Delete
+
+
+
+
+
+
+ Argentina
+ Bulgaria
+ China
+
+
+
+
+
+
+ Inherits menuitem
+ Separator-like (role=none)
+ Inherits menuitem
+
+
+
+
+
+
+
+
diff --git a/packages/website/docs/_samples/main/List/AccessibleRole/sample.tsx b/packages/website/docs/_samples/main/List/AccessibleRole/sample.tsx
new file mode 100644
index 000000000000..605b8f8cbcc7
--- /dev/null
+++ b/packages/website/docs/_samples/main/List/AccessibleRole/sample.tsx
@@ -0,0 +1,45 @@
+import createReactComponent from "@ui5/webcomponents-base/dist/createReactComponent.js";
+import ListClass from "@ui5/webcomponents/dist/List.js";
+import ListItemStandardClass from "@ui5/webcomponents/dist/ListItemStandard.js";
+import "@ui5/webcomponents-icons/dist/create.js";
+import "@ui5/webcomponents-icons/dist/save.js";
+import "@ui5/webcomponents-icons/dist/delete.js";
+import "@ui5/webcomponents-icons/dist/filter.js";
+
+const List = createReactComponent(ListClass);
+const ListItemStandard = createReactComponent(ListItemStandardClass);
+
+function App() {
+ return (
+ <>
+ {/* Menu: list role="menu", items inherit role="menuitem" */}
+
+ New Document
+ Save
+ Delete
+
+
+
+
+ {/* ListBox: list role="listbox", items inherit role="option" */}
+
+ Argentina
+ Bulgaria
+ China
+
+
+
+
+ {/* Explicit accessible-role on ui5-li overrides the inherited role */}
+
+ Inherits menuitem
+
+ Separator-like (role=none)
+
+ Inherits menuitem
+
+ >
+ );
+}
+
+export default App;