From ce8ed89fdc2f73a29da943e19c020b395cda97ed Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Thu, 24 Sep 2026 13:06:10 -0400 Subject: [PATCH] fix(a11y): 36 modal dialogs announce themselves and take focus (#185) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most modals rendered a folds Dialog/Modal with no role and no name, and their focus traps used `initialFocus: false`, so focus stayed behind the modal and a screen reader never announced it. - 32 dialogs with a visible heading: role="dialog", aria-modal, aria-labelledby → the heading (given an id), tabIndex=-1. - 4 dialogs that already had a name (Leave Room, room topic viewer, server ACL, room-nav prompt): role + aria-modal. - Their focus traps drop `initialFocus: false` for focus-trap's default (keep an already-focused autoFocus field, else the first tabbable element) with the dialog itself as fallbackFocus, so a dialog without a tabbable node can't crash the trap. Traps that live in a parent (UIA stages, Logout, Forward, Invite) get the semantics only. - The file drop overlay is deliberately left alone (not a dialog). Checked at runtime: Join with Address, Delete Message, Report Message, Leave Room and Logout open as named dialogs with focus inside and close with Escape (Tab first when a text field has focus — the shared stopPropagation keeps Escape from discarding typed text, by design). The axe e2e spec (6 tests) passes; eslint warnings unchanged (46). 17 modals with no heading (image/file viewers, loading screens) remain. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- src/app/components/DeviceVerification.tsx | 14 +++++++-- .../components/DeviceVerificationSetup.tsx | 24 +++++++++++--- src/app/components/LogoutDialog.tsx | 13 ++++++-- .../invite-user-prompt/InviteUserPrompt.tsx | 11 +++++-- .../join-address-prompt/JoinAddressPrompt.tsx | 14 +++++++-- .../leave-room-prompt/LeaveRoomPrompt.tsx | 12 +++++-- .../leave-space-prompt/LeaveSpacePrompt.tsx | 14 +++++++-- .../room-topic-viewer/RoomTopicViewer.tsx | 4 +++ .../suspicious-link/SuspiciousLink.tsx | 14 +++++++-- src/app/components/uia-stages/DummyStage.tsx | 10 ++++-- src/app/components/uia-stages/EmailStage.tsx | 20 +++++++++--- .../components/uia-stages/PasswordStage.tsx | 10 ++++-- .../components/uia-stages/ReCaptchaStage.tsx | 12 +++++-- .../uia-stages/RegistrationTokenStage.tsx | 10 ++++-- src/app/components/uia-stages/SSOStage.tsx | 10 ++++-- src/app/components/uia-stages/TermsStage.tsx | 10 ++++-- src/app/components/user-profile/PowerChip.tsx | 26 ++++++++++++---- src/app/components/user-profile/UserHero.tsx | 8 ++++- src/app/features/add-existing/AddExisting.tsx | 14 +++++++-- .../general/DeleteMyMessages.tsx | 14 +++++++-- .../general/RoomEncryption.tsx | 14 +++++++-- .../common-settings/general/RoomUpgrade.tsx | 14 +++++++-- .../features/create-room/CreateRoomModal.tsx | 15 +++++++-- .../create-space/CreateSpaceModal.tsx | 15 +++++++-- src/app/features/room-nav/RoomNavItem.tsx | 11 +++++-- .../features/room-settings/RoomServerACL.tsx | 6 +++- .../features/room/jump-to-time/JumpToTime.tsx | 16 ++++++++-- .../room/message/ForwardMessageDialog.tsx | 7 ++++- src/app/features/room/message/Message.tsx | 31 +++++++++++++++---- src/app/features/settings/account/Profile.tsx | 28 ++++++++++++++--- src/app/pages/client/explore/Explore.tsx | 13 ++++++-- 31 files changed, 350 insertions(+), 84 deletions(-) diff --git a/src/app/components/DeviceVerification.tsx b/src/app/components/DeviceVerification.tsx index f2a348711..758c95c7f 100644 --- a/src/app/components/DeviceVerification.tsx +++ b/src/app/components/DeviceVerification.tsx @@ -365,15 +365,23 @@ export function DeviceVerification({ request, onExit }: DeviceVerificationProps) - +
- + Device Verification diff --git a/src/app/components/DeviceVerificationSetup.tsx b/src/app/components/DeviceVerificationSetup.tsx index b0308b866..d8f9f50ee 100644 --- a/src/app/components/DeviceVerificationSetup.tsx +++ b/src/app/components/DeviceVerificationSetup.tsx @@ -293,7 +293,15 @@ export const DeviceVerificationSetup = forwardRef +
- + Setup Device Verification @@ -331,7 +339,15 @@ export const DeviceVerificationReset = forwardRef +
- + Reset Device Verification diff --git a/src/app/components/LogoutDialog.tsx b/src/app/components/LogoutDialog.tsx index 4d92ab3e9..921e595ec 100644 --- a/src/app/components/LogoutDialog.tsx +++ b/src/app/components/LogoutDialog.tsx @@ -52,7 +52,16 @@ export const LogoutDialog = forwardRef( const ongoingLogout = logoutState.status === AsyncStatus.Loading; return ( - +
( size="500" > - + Logout diff --git a/src/app/components/invite-user-prompt/InviteUserPrompt.tsx b/src/app/components/invite-user-prompt/InviteUserPrompt.tsx index 4e96d985f..4e110be41 100644 --- a/src/app/components/invite-user-prompt/InviteUserPrompt.tsx +++ b/src/app/components/invite-user-prompt/InviteUserPrompt.tsx @@ -189,14 +189,21 @@ export function InviteUserPrompt({ room, requestClose }: InviteUserProps) { escapeDeactivates: stopPropagation, }} > - +
- + {t('Organisms.InviteUser.invite')} diff --git a/src/app/components/join-address-prompt/JoinAddressPrompt.tsx b/src/app/components/join-address-prompt/JoinAddressPrompt.tsx index 88d537ca8..921f57024 100644 --- a/src/app/components/join-address-prompt/JoinAddressPrompt.tsx +++ b/src/app/components/join-address-prompt/JoinAddressPrompt.tsx @@ -67,13 +67,21 @@ export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) { - +
- + Join with Address diff --git a/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx b/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx index f4c63cde3..4010aa192 100644 --- a/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx +++ b/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx @@ -52,13 +52,21 @@ export function LeaveRoomPrompt({ roomId, onDone, onCancel }: LeaveRoomPromptPro - +
- +
- + Leave Space diff --git a/src/app/components/room-topic-viewer/RoomTopicViewer.tsx b/src/app/components/room-topic-viewer/RoomTopicViewer.tsx index 974b1daed..a5ee48dec 100644 --- a/src/app/components/room-topic-viewer/RoomTopicViewer.tsx +++ b/src/app/components/room-topic-viewer/RoomTopicViewer.tsx @@ -26,6 +26,10 @@ export const RoomTopicViewer = as< return ( setOpen(false), clickOutsideDeactivates: true, escapeDeactivates: stopPropagation, }} > - +
- + This link doesn't go where it says diff --git a/src/app/components/uia-stages/DummyStage.tsx b/src/app/components/uia-stages/DummyStage.tsx index 46f5490e3..6f493a5fb 100644 --- a/src/app/components/uia-stages/DummyStage.tsx +++ b/src/app/components/uia-stages/DummyStage.tsx @@ -15,10 +15,16 @@ function DummyErrorDialog({ onCancel: () => void; }) { return ( - + - + {title} {message} diff --git a/src/app/components/uia-stages/EmailStage.tsx b/src/app/components/uia-stages/EmailStage.tsx index 9c9ec60ab..7acd00c66 100644 --- a/src/app/components/uia-stages/EmailStage.tsx +++ b/src/app/components/uia-stages/EmailStage.tsx @@ -28,7 +28,13 @@ function EmailErrorDialog({ }; return ( - + - + {title} {message} @@ -145,10 +151,16 @@ export function EmailStageDialog({ if (emailTokenState.status === AsyncStatus.Success) { return ( - + - + Verification Request Sent {`Please check your email "${emailTokenState.data.email}" and validate before continuing further.`} diff --git a/src/app/components/uia-stages/PasswordStage.tsx b/src/app/components/uia-stages/PasswordStage.tsx index c3fd5c38a..8a468ce9a 100644 --- a/src/app/components/uia-stages/PasswordStage.tsx +++ b/src/app/components/uia-stages/PasswordStage.tsx @@ -36,7 +36,13 @@ export function PasswordStage({ }; return ( - +
- + {t('Organisms.PasswordStage.account_password')} diff --git a/src/app/components/uia-stages/ReCaptchaStage.tsx b/src/app/components/uia-stages/ReCaptchaStage.tsx index 68b3fcf48..4abae18ed 100644 --- a/src/app/components/uia-stages/ReCaptchaStage.tsx +++ b/src/app/components/uia-stages/ReCaptchaStage.tsx @@ -14,10 +14,18 @@ function ReCaptchaErrorDialog({ onCancel: () => void; }) { return ( - + - {title} + + {title} + {message}