Skip to content

Comments

feat(ui5-li-custom): improve accessibility announcements#12696

Merged
IlianaB merged 24 commits intomainfrom
list-item-custom-announcements
Feb 11, 2026
Merged

feat(ui5-li-custom): improve accessibility announcements#12696
IlianaB merged 24 commits intomainfrom
list-item-custom-announcements

Conversation

@IlianaB
Copy link
Contributor

@IlianaB IlianaB commented Nov 19, 2025

related to #7729

@ui5-webcomponents-bot
Copy link
Collaborator

ui5-webcomponents-bot commented Nov 19, 2025

🧹 Preview deployment cleaned up: https://pr-12696--ui5-webcomponents.netlify.app

@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview November 19, 2025 15:02 Inactive
@IlianaB IlianaB changed the title wip(ui5-li-custom): improve accessibility announcements feat(ui5-li-custom): improve accessibility announcements Dec 8, 2025
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview December 9, 2025 09:33 Inactive
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview December 16, 2025 10:11 Inactive
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview December 17, 2025 14:42 Inactive
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview January 6, 2026 11:34 Inactive
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview January 6, 2026 12:52 Inactive
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview January 6, 2026 15:01 Inactive
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview January 6, 2026 15:09 Inactive
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview January 6, 2026 15:16 Inactive
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview January 7, 2026 08:08 Inactive
@aborjinik
Copy link
Contributor

why don't we reuse this? We could externalize it from the table.
https://github.com/UI5/webcomponents/blob/main/packages/main/src/TableCustomAnnouncement.ts#L55

@IlianaB
Copy link
Contributor Author

IlianaB commented Jan 20, 2026

why don't we reuse this? We could externalize it from the table. https://github.com/UI5/webcomponents/blob/main/packages/main/src/TableCustomAnnouncement.ts#L55

We discussed with the team: we could use an externalized logic for extracting accessibility descriptions. The only concern - currently there is a specific table-related logic. Can it be separated from the externalized method for processing the accessibility info?

@aborjinik
Copy link
Contributor

Actually, I do not see any table-specific logic here. But if such logic exists, it should go into the options (we should probably use the second parameter of getAccessibilityDescription for options anyway).

  1. data-ui5-table-acc-text (we could generalize this to data-ui5-acc-text) can be used for native elements (or web components) to provide (or override) accessibility information.
  2. If there is nothing inside the custom list item, I think it would be acceptable to announce it as "Empty".
  3. If we cannot retrieve the accessibility information of an element and the default accessibility logic does not return any text, but the element is tabbable (for example, a simple native <input> element), we should not announce it as empty. Instead we announce something like "contains control", so the user knows that F2 can be used to jump into it for more details.

As mentioned I do not see those as table-specific.

@IlianaB
Copy link
Contributor Author

IlianaB commented Jan 22, 2026

Actually, I do not see any table-specific logic here. But if such logic exists, it should go into the options (we should probably use the second parameter of getAccessibilityDescription for options anyway).

  1. data-ui5-table-acc-text (we could generalize this to data-ui5-acc-text) can be used for native elements (or web components) to provide (or override) accessibility information.
  2. If there is nothing inside the custom list item, I think it would be acceptable to announce it as "Empty".
  3. If we cannot retrieve the accessibility information of an element and the default accessibility logic does not return any text, but the element is tabbable (for example, a simple native <input> element), we should not announce it as empty. Instead we announce something like "contains control", so the user knows that F2 can be used to jump into it for more details.

As mentioned I do not see those as table-specific.

Thanks for explanations @aborjinik . Let us know when it’s ready for use. We’ll try to adapt. If any questions or difficulties arise, we’ll contact you. :)

@aborjinik
Copy link
Contributor

Let us know when it’s ready for use.

I am a little confused. I am not fully aware of all your requirements. Are you expecting us to provide a utility that can be reused by other components?

@aborjinik
Copy link
Contributor

Let us know when it’s ready for use.

Please see #12976
You can adjust the options parameters to fit your needs, as long as they remain compatible with the table usage.
I would suggest placing a list with custom list items containing different content inside a ui5-table-cell and verifying whether the resulting custom announcement from the cell meets the requirements

@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview February 2, 2026 14:56 Inactive
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview February 2, 2026 15:25 Inactive
_onfocusin(e: FocusEvent) {
super._onfocusin(e);
// Skip updating invisible text during drag operations
if (!this._isDragging()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

how the component is focused during dragging? that would be nice to know for the table

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a safety check due to consistently failing tests in TabContainerDragAndDrop.cy.tsx and TabContainerDragAndDropShadowDom.cy.tsx - it seems like rendering (changing text of invisible span) steals the focus in test environment and check for currently focused element fails. I wasn't able to reproduce any actual problem when trying to drag and drop list items in test pages.

super._onfocusin(e);
// Skip updating invisible text during drag operations
if (!this._isDragging()) {
this._updateInvisibleTextContent();
Copy link
Contributor

Choose a reason for hiding this comment

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

isn't this event bubble? from an input that is in the list item for example? for this case you do not need the custom announcement.

Comment on lines +127 to +145
onAfterRendering() {
// This will run after the component is rendered
if (this.shadowRoot && !this.shadowRoot.querySelector(`#${this._id}-invisibleTextContent`)) {
const span = document.createElement("span");
span.id = `${this._id}-invisibleTextContent`;
span.className = "ui5-hidden-text";
// Empty content as requested
this.shadowRoot.appendChild(span);
}
}

/**
* Returns the invisible text span element used for accessibility announcements
* @returns {HTMLElement | null} The HTMLElement representing the invisible text span used for accessibility announcements, or null if the element is not found in the shadow DOM
* @private
*/
private get _invisibleTextSpan(): HTMLElement | null {
return this.shadowRoot?.querySelector(`#${this._id}-invisibleTextContent`) as HTMLElement;
}
Copy link
Contributor

@aborjinik aborjinik Feb 10, 2026

Choose a reason for hiding this comment

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

why don't you just use applyCustomAnnouncement ? You cannot focus two elements at the same time. It is not necessary to create a similar node for every list

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@aborjinik , when I first started working on this task I checked the table logic. Reusing just one span for every announcement was the solution I liked the most. Unfortunately, with the already existing implementation of all ListItems, following this logic would require refactoring on all of them, which we decided would unnecessary expand the scope of this task.

If we decide to create a refactoring task - this suggestion would be part of it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately, with the already existing implementation of all ListItems, following this logic would require refactoring on all of them

Ok then I guess I do not understand what this custom announcement is good for and how it does not mix up with the original announcement.

if (defaultSlot) {
const assignedNodes = (defaultSlot as HTMLSlotElement).assignedNodes({ flatten: true });
assignedNodes.forEach(child => {
const text = getCustomAnnouncement(child, { lessDetails: false }, false);
Copy link
Contributor

Choose a reason for hiding this comment

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

please do not call with default values and the 3rd parameter is private

// Return the built-in delete button from the shadow DOM if it exists
const deleteButton = this.shadowRoot?.querySelector(`#${this._id}-deleteSelectionElement`);
return deleteButton ? [deleteButton] : [];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead, I think you should just implement the accessibilityInfo, if any customization necessary (seems like something special with the delete button nodes) then only in this case this should be reflected in the children array accordingly. The type property of the accessibilityInfo should be taken from the LISTITEMCUSTOM_TYPE_TEXT and then you should just ask the `getCustomAnnouncement(this). You do not need to build your own custom announcement again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have tried with accessibilityInfo implementation on the ListItemCustom itself. It's not the deleteButtons, which bothered me. I have just included them in the children array.

We decided we do not these logic for rootElement:
if (_isRootElement) {
const hasDescription = descriptions.length > 0;
if (!hasDescription || !lessDetails) {
const tabbables = getTabbableElements(element);
const bundleKey = [
hasDescription ? "" : ACC_STATE_EMPTY,
ACC_STATE_SINGLE_CONTROL,
ACC_STATE_MULTIPLE_CONTROLS,
][Math.min(tabbables.length, 2)];
if (bundleKey) {
hasDescription && descriptions.push(".");
descriptions.push(getBundle().getText(bundleKey));
}
}
}

Single control or Multiple controls - checked with ACC experts - we do not have such requirement for now. But because of the check || !lessDetails - we cannot omit this additional information (as we need more details on the children level - required, disabled, etc states).
To be honest I do not completely understand the check here - how the lessDetails are related to this additional information for single/multiple controls or empty state and the same time with the required/disabled/readonly states. Maybe if there are two different properties of the CustomAnnouncementOptions? lessDetails and accStates?
Empty state - seemed nice but we can never pass the conditions here as we will always have a description (coming from the type property of ListItemCustom's acc info).

Copy link
Contributor

@aborjinik aborjinik Feb 10, 2026

Choose a reason for hiding this comment

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

Single control or Multiple controls - checked with ACC experts - we do not have such requirement for now.

In a custom list item put a single native <input>, or <ui5-input> which did not implement the accessibilityInfo yet. When the focus set on that custom list item what should be the announcement?
Or just put nothing inside, and you focus on this CLI then what should be announced?
This is what that logic handles.
Besides that if one day ui5-input implements the accessibilityInfo then it is also true that you will hear "Includes Element" after the input announcement. This is something to notify users that there is something interactive inside this component so they can press F2 to jump in and get more details. But if you do not want that then you can add an option to disable that feature.

Empty state - seemed nice but we can never pass the conditions here as we will always have a description (coming from the type property of ListItemCustom's acc info).

True and can be fixed in getCustomAnnouncement but you can also consider to use aria-roledescription and avoid the type from the custom announcement.

@IlianaB IlianaB merged commit f6e6981 into main Feb 11, 2026
14 checks passed
@IlianaB IlianaB deleted the list-item-custom-announcements branch February 11, 2026 08:20
@ui5-webcomponents-bot ui5-webcomponents-bot temporarily deployed to preview February 11, 2026 08:20 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants