Skip to main content
All Docs
FeaturesMaking Tax DigitalUpdated March 25, 2026

Accessibility: Focus Management in Dialogs

Accessibility: Focus Management in Dialogs

As of v1.0.441, all modal dialogs in the Making Tax Digital platform implement a full WCAG 2.1.2-compliant focus trap. This ensures keyboard-only users and assistive technology users cannot accidentally interact with background content while a dialog is open.

Affected Components

ComponentLocationDialog Type
ConfirmDialogsrc/components/confirm-dialog.tsxGeneral-purpose confirmation (e.g. archive property)
DeleteConfirmDialogsrc/app/dashboard/transactions/delete-confirm-dialog.tsxDestructive delete for manual transactions
MfaVerifyModalsrc/components/mfa-verify-modal.tsxMFA code entry before HMRC submission

The useFocusTrap Hook

All three dialogs share a single useFocusTrap hook (src/hooks/use-focus-trap.ts). The hook accepts three parameters:

useFocusTrap({
  open: boolean,         // whether the dialog is currently open
  onClose: () => void,   // callback to close the dialog
  containerRef: RefObject<HTMLElement>,  // ref to the dialog container
});

Behaviour

  • Tab cycling — pressing Tab from the last focusable element in the container wraps focus back to the first. Shift+Tab from the first element wraps to the last.
  • Escape to close — pressing Escape calls onClose, closing the dialog and returning focus.
  • Focus save/restore — when the dialog opens, the currently focused element (document.activeElement) is saved. When the dialog closes (including via Escape), focus is returned to that element.
  • Body scroll lockdocument.body.style.overflow is set to hidden while the dialog is open, preventing background content from scrolling.
  • aria-hidden filtering — elements marked aria-hidden="true" are excluded from the focusable element query, matching the same pattern used in the platform's base modal.tsx.

WCAG Compliance

WCAG CriterionRequirementImplementation
2.1.1 KeyboardAll functionality must be operable via keyboardAll dialog actions (confirm, cancel, close) are reachable by Tab alone
2.1.2 No Keyboard TrapIf focus can be moved into a component, it must be movable out againEscape key always closes the dialog; focus cycles within the container but never escapes to background content
2.4.3 Focus OrderFocus order must preserve meaning and operabilityFocus moves into the dialog on open and returns to the trigger element on close

Dialog-Specific Notes

ConfirmDialog

Used for confirmations such as archiving a property. Tab cycles through the close button (×), Cancel, and Confirm. Previously relied on three separate useEffect hooks; these have been replaced by a single useFocusTrap call.

DeleteConfirmDialog

Used when permanently deleting a manual transaction from the transaction ledger. The component was previously defined inline inside transaction-ledger.tsx with only a partial Escape-key handler and no Tab cycling. It has been extracted to its own file and upgraded with the full focus trap.

Tab cycles between Cancel and Delete.

MfaVerifyModal

Shown before an HMRC submission when MFA is enabled. The modal is always open when rendered — the parent component controls mounting. Tab focus is contained within the panel div. All action buttons carry visible focus-ring styles (focus:ring-2 focus:ring-ring).

Testing Keyboard Focus

To manually verify focus trap behaviour:

  1. ConfirmDialog — trigger an archive action on a property. Tab should cycle through ×, Cancel, and Confirm. Pressing Tab from Confirm should wrap back to ×. Pressing Escape should close the dialog and return focus to the button that opened it.

  2. DeleteConfirmDialog — initiate deletion of a manual transaction. Tab should cycle between Cancel and Delete. Escape should cancel and restore focus.

  3. MfaVerifyModal — begin an HMRC submission with MFA enabled. Tab should cycle through all interactive elements within the modal panel. Escape should cancel the modal.