From a4ad49bfb0f3ef4c1a8a985427bf4c4598aaed6a Mon Sep 17 00:00:00 2001 From: Lukas Harbarth Date: Mon, 22 Jun 2026 12:14:00 +0200 Subject: [PATCH] docs(SelectDialog): add example for announcing search result count --- .../components/SelectDialog/SelectDialog.mdx | 87 +++++++++++++++++++ .../SelectDialog/SelectDialog.stories.tsx | 76 +++++++++++++++- 2 files changed, 162 insertions(+), 1 deletion(-) diff --git a/packages/main/src/components/SelectDialog/SelectDialog.mdx b/packages/main/src/components/SelectDialog/SelectDialog.mdx index a76d0b98145..464adc48d46 100644 --- a/packages/main/src/components/SelectDialog/SelectDialog.mdx +++ b/packages/main/src/components/SelectDialog/SelectDialog.mdx @@ -172,4 +172,91 @@ const MultiSelectDialog = () => {

+### Announcing search result count + +A common a11y requirement is to announce the number of matches when the user filters the list (e.g. _"5 results available"_, _"No results found"_). + +The framework's `InvisibleMessage.announce(...)` cannot be used here: its live region (``) is appended to `document.body`, outside the dialog. When the dialog is open, some screen readers do not announce updates from live regions rendered outside it, so the message is silently dropped. See [UI5/webcomponents#13613](https://github.com/UI5/webcomponents/issues/13613) for details. + +The workaround is to render your own `aria-live="polite"` region **inside** the dialog's DOM via `createPortal`, and write the message into it from your search handler. + + + +
+ +Show Code + +```tsx +const items = Array.from({ length: 40 }, (_unused, index) => ({ + id: `P-${index.toString().padStart(3, '0')}`, + text: ['Gaming Laptop', 'Business Laptop', 'Gaming PC', 'Business PC'][index % 4], +})); + +const liveRegionStyle: CSSProperties = { + position: 'absolute', + clip: 'rect(1px,1px,1px,1px)', + userSelect: 'none', + left: '-1000px', + top: '-1000px', + pointerEvents: 'none', +}; + +const SearchAnnouncementDialog = () => { + const [dialogEl, setDialogEl] = useState(null); + const liveSpanRef = useRef(null); + const [open, setOpen] = useState(false); + const [searchValue, setSearchValue] = useState(''); + + const filteredItems = useMemo(() => { + const query = searchValue.trim().toLowerCase(); + if (!query) { + return items; + } + return items.filter((item) => item.id.toLowerCase().includes(query) || item.text.toLowerCase().includes(query)); + }, [searchValue]); + + const announceCount = (count: number) => { + const span = liveSpanRef.current; + if (!span) { + return; + } + span.textContent = count === 0 ? 'No results found' : `${count} results available`; + }; + + const handleSearch: SelectDialogPropTypes['onSearch'] = (event) => { + setSearchValue(event.detail.value); + announceCount(filteredItems.length); + }; + + const handleSearchReset = () => { + setSearchValue(''); + announceCount(items.length); + }; + + return ( + <> + + setOpen(false)} + onSearch={handleSearch} + onSearchReset={handleSearchReset} + > + {filteredItems.map((item) => ( + + ))} + + {dialogEl && createPortal(, dialogEl)} + + ); +}; +``` + +
+ +
+
+