Skip to content

VoiceConfirmModal is a role="dialog" with aria-modal="true" but has no keyboard focus trap #1059

Description

@mikewheeleer

Description

src/components/voice/VoiceConfirmModal.tsx declares full modal-dialog semantics and auto-focuses its primary action on open:

return (
  <div
    role="dialog"
    aria-modal="true"
    aria-labelledby="voice-confirm-heading"
    aria-describedby="voice-confirm-desc"
    className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm animate-fade-in"
  >

with an Escape key handler to cancel and a focus-on-open effect for the Confirm button — but there is no Tab/Shift+Tab keydown handling anywhere in the file to keep focus cycling within the dialog's two buttons (Confirm / Cancel). A keyboard user pressing Tab repeatedly from either button will tab straight out of the modal into the underlying page content while the modal overlay is still visually blocking interaction, violating the implicit contract of aria-modal="true" and WCAG 2.4.3 (Focus Order). This is a distinct file/component from any other modal focus-trap issue already tracked elsewhere in the codebase (e.g. InfoTooltip's popover), since no shared focus-trap utility exists for either to reuse.

Requirements

  • VoiceConfirmModal must trap keyboard focus within its two interactive elements while open — Tab from the last focusable element wraps to the first, and Shift+Tab from the first wraps to the last.
  • Existing Escape-to-cancel and auto-focus-on-open behavior must be preserved.

Suggested execution

  1. Add a Tab/Shift+Tab keydown handler in VoiceConfirmModal.tsx's existing useEffect (alongside the current Escape handling) that queries the dialog's focusable elements (Confirm and Cancel buttons, plus the close X button) and wraps focus at the boundaries, mirroring the focus-cycling logic already implemented in CreateStreamFab.tsx's menu (handleMenuKeyDown) for a similar pattern in this same codebase.
  2. Alternatively, extract a small shared useFocusTrap hook from this implementation so future modals (and CreateStreamFab's menu) can share it instead of duplicating the cycling logic.
  3. Add a test that opens the modal, tabs past the last focusable element, and asserts focus wraps back to the first rather than leaving the dialog.

Acceptance criteria

  • Tab/Shift+Tab cannot move focus outside VoiceConfirmModal while it is open.
  • Existing Escape-to-cancel and auto-focus behavior is unchanged.
  • A regression test covers focus wrapping in both directions.

Security notes

None; accessibility correctness fix.

Guidelines

  • Minimum 95% test coverage
  • Timeframe: 96 hours

Metadata

Metadata

Assignees

No one assigned

    Labels

    GRANTFOX OSSGrantFox open-source campaign taskMAYBE REWARDEDMay be rewarded under the GrantFox campaignOfficial Campaign | FWC26GrantFox FWC26 official campaigna11ybugSomething isn't workingfrontend

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions