Lookup - Screen readers do not pick up NoDataText when Lookup is empty - #35026
Lookup - Screen readers do not pick up NoDataText when Lookup is empty#35026dmlvr wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The change is narrowly scoped, includes targeted automated test coverage for the new accessibility behavior, and updates dependent assertions/snapshots accordingly.
Pull request overview
This PR improves List/Lookup accessibility by introducing a dedicated, visually hidden live region (role="status") in the List base implementation so screen readers reliably announce list state changes—particularly noDataText when a Lookup search yields no results.
Changes:
- Added a persistent screen-reader-only live region to
ListBaseand updated its text based on loading state, item count, andnoDataText. - Updated existing List QUnit tests that previously asserted
element.text()(now affected by the live region text). - Added new QUnit coverage for announcements in both List and Lookup, and updated affected Jest snapshots.
File summaries
| File | Description |
|---|---|
| packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js | Adjusts text assertions to ignore the new live-region text; adds QUnit accessibility tests for announcements. |
| packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js | Adds QUnit tests verifying Lookup’s internal list announces item count / noDataText on search without recreating the live region. |
| packages/devextreme/js/__internal/ui/list/list.base.ts | Implements the live region container and announcement updates in ListBase. |
| packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/snapshots/view.integration.test.tsx.snap | Updates snapshots to include the newly rendered live region in lists used by header filter UI. |
| packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/snapshots/options.integration.test.ts.snap | Updates snapshots to include the newly rendered live region in lists used by header filter UI. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const itemsCount = this._editStrategy.itemsGetter().length; | ||
| const itemsLabel = messageLocalization.format('dxList-listAriaLabel'); | ||
|
|
||
| this._$a11yStatusContainer?.text(itemsCount ? `${itemsLabel}: ${itemsCount}` : noDataText ?? ''); |
There was a problem hiding this comment.
Check what we need to announce.
| const itemsCount = this._editStrategy.itemsGetter().length; | ||
| const itemsLabel = messageLocalization.format('dxList-listAriaLabel'); | ||
|
|
||
| this._$a11yStatusContainer?.text(itemsCount ? `${itemsLabel}: ${itemsCount}` : noDataText ?? ''); |
| const LIST_SELECT_RADIOBUTTON = 'dx-list-select-radiobutton'; | ||
| const WRAP_ITEM_TEXT_CLASS = 'dx-wrap-item-text'; | ||
| const SELECT_ALL_ITEM_SELECTOR = '.dx-list-select-all'; | ||
| export const SCREEN_READER_ONLY_CLASS = 'dx-screen-reader-only'; |
There was a problem hiding this comment.
Check if it can be imported.
There was a problem hiding this comment.
we have two places where it's usable:
- https://github.com/DevExpress/DevExtreme/blob/main/packages/devextreme/js/__internal/ui/validation_summary.ts#L20
- https://github.com/DevExpress/DevExtreme/blob/main/packages/devextreme/js/__internal/ui/calendar/calendar.views.ts#L21
but I'm not sure import from this places right idea. What do you think?
| const LIST_SELECT_RADIOBUTTON = 'dx-list-select-radiobutton'; | ||
| const WRAP_ITEM_TEXT_CLASS = 'dx-wrap-item-text'; | ||
| const SELECT_ALL_ITEM_SELECTOR = '.dx-list-select-all'; | ||
| export const SCREEN_READER_ONLY_CLASS = 'dx-screen-reader-only'; |
There was a problem hiding this comment.
Check if we can reuse dx-gridbase-a11y-status-container
There was a problem hiding this comment.
this class was created for similar cases
| _renderA11yStatusContainer(): void { | ||
| const isContainerExistingInDOM = this._$a11yStatusContainer?.parent().is(this.$element()); | ||
|
|
||
| if (isContainerExistingInDOM) { |
There was a problem hiding this comment.
if(!!this._$a11yStatusContainer.get(0))
| @@ -85,6 +85,7 @@ const LIST_SELECT_CHECKBOX = 'dx-list-select-checkbox'; | |||
| const LIST_SELECT_RADIOBUTTON = 'dx-list-select-radiobutton'; | |||
There was a problem hiding this comment.
🔵 Needs a closer look
The new live-region announcement label is inconsistent with existing ARIA labeling behavior for deletable lists (allowItemDeleting), which should be aligned before merging.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
packages/devextreme/js/__internal/ui/list/list.base.ts:1030
_updateA11yStatusTextalways usesdxList-listAriaLabelfor the announcement label, but_setListAria()switches todxList-listAriaLabel-deletablewhenallowItemDeletingis enabled. This makes the live-region announcement inconsistent with the list’s own ARIA label in deletable mode (and may produce partially untranslated output in that configuration). Consider selecting the same label key here based onallowItemDeleting.
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
The live-region element can be detached during widget refresh/clean but not recreated due to a stale cached reference, which can break announcements after rerender.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
| _renderA11yStatusContainer(): void { | ||
| if (this._$a11yStatusContainer?.get(0)) { | ||
| return; | ||
| } | ||
|
|
||
| this._$a11yStatusContainer = $('<div>') | ||
| .addClass(SCREEN_READER_ONLY_CLASS) | ||
| .attr('role', 'status') | ||
| .appendTo(this.$element()); | ||
| } |

No description provided.