diff --git a/x-pack/solutions/security/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx index 290d9e02e7eed..b6ae7cb10dd75 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx @@ -19,6 +19,7 @@ import { EuiPageHeader, EuiPagination, EuiPopover, + EuiScreenReaderLive, EuiSpacer, EuiText, } from '@elastic/eui'; @@ -148,6 +149,7 @@ export const SharedLists = React.memo(() => { const [exportDownload, setExportDownload] = useState<{ name?: string; blob?: Blob }>({}); const [displayImportListFlyout, setDisplayImportListFlyout] = useState(false); + const [screenReaderMessage, setScreenReaderMessage] = useState(''); const { addError, addSuccess } = useAppToasts(); // Loading states @@ -202,7 +204,18 @@ export const SharedLists = React.memo(() => { const handleExportSuccess = useCallback( (listId: string, name: string) => (blob: Blob): void => { - addSuccess(i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(name)); + const message = i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(name); + addSuccess(message); + // If the same list is exported twice in a row, React won't re-render + // since state hasn't changed, so the live region won't re-announce. + // Clearing to '' first ensures VoiceOver sees a new change on the next frame. + setScreenReaderMessage((current) => { + if (current === message) { + requestAnimationFrame(() => setScreenReaderMessage(message)); + return ''; + } + return message; + }); setExportDownload({ name: listId, blob }); }, [addSuccess] @@ -468,6 +481,9 @@ export const SharedLists = React.memo(() => { return ( <> + + {screenReaderMessage} +