Skip to content

fix(dialog): add focus-trap and ARIA attributes to ZardDialogComponent - #87

Open
Aarti-panchal01 wants to merge 1 commit into
angular-zard-migrationfrom
fix/dialog-focus-trap
Open

fix(dialog): add focus-trap and ARIA attributes to ZardDialogComponent#87
Aarti-panchal01 wants to merge 1 commit into
angular-zard-migrationfrom
fix/dialog-focus-trap

Conversation

@Aarti-panchal01

Copy link
Copy Markdown
Member

Summary

ZardDialogComponent (v2/ui/dialog/dialog.component.ts) is built directly on @angular/cdk/overlay, not MatDialog/CdkDialog, so it never got any of Angular Material's automatic dialog accessibility behavior. Confirmed via investigation: zero role, zero aria-modal, zero aria-labelledby/aria-describedby, and zero focus trap — Tab could walk out of an open dialog into the page behind the backdrop, nothing moved focus into the dialog on open, and nothing restored it on close. This affects every dialog in every consumer app of this shared library (confirm dialogs, alerts, etc.).

Escape-to-close and backdrop-click were already implemented in dialog-ref.ts and are untouched here.

Changes

  1. role="dialog" and aria-modal="true" added as static host attributes.
  2. aria-labelledby/aria-describedby added as host bindings, pointing at new ids on the title/description elements — conditionally set only when a title/description is actually configured (matches the existing conditional-render logic, doesn't change it).
  3. id attributes added to the title (<h4 data-testid="z-title">) and description (<p data-testid="z-description">) elements, uniqued per dialog instance via a simple module-scoped counter (a component could theoretically have more than one dialog open at once).
  4. CDK FocusTrapFactory wired in: creates a real FocusTrap on the host element in ngAfterViewInit() (after the container's own view — and, for component/template content, its synchronously-attached portal content — is in the DOM), destroys it in ngOnDestroy().
  5. Auto-focus on open: focusTrap.focusInitialElementWhenReady() — CDK's own "focus cdkFocusInitial element, else first tabbable" behavior, matching MatDialog's default AutoFocusTarget.FIRST_TABBABLE.
  6. Focus restoration on close: captures document.activeElement right before trapping (in ngAfterViewInit(), before anything else can move focus) and restores it in ngOnDestroy(). HTMLElement.focus() on a since-detached element is a no-op, so no extra guard is needed there.

This matches MatDialog's actual behavior as directly observed on the legacy app's live UAT: role="dialog", aria-labelledby wired to the title, real CDK-backed focus trap (confirmed via the presence of .cdk-focus-trap-anchor sentinel elements), initial focus onto the dialog on open, and focus moving to a real element after close — all reproduced here. (Legacy itself never set aria-modal or aria-describedby either, so this implementation is a strict superset of legacy's own behavior, not just a match.)

Verification

@angular/cdk/a11y is the same @angular/cdk package already used elsewhere in this file (@angular/cdk/overlay, @angular/cdk/portal) — no new dependency. This repo has no standalone build/lint of its own (pure source, consumed via path alias); verified against the consuming app instead:

  • ng build (Helpline104-UI-NEXT) — clean, zero errors.
  • ng test (Helpline104-UI-NEXT) — 49/49 passing, no regressions from the real focus trap now being active in tests that render a dialog.

Manual follow-up needed

None for this change itself. Consuming apps (e.g. Helpline104-UI-NEXT) will get real focus-trap/ARIA behavior automatically once this is merged and their submodule pin is bumped — no code change needed on their end.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 8d2e05de-9ff5-4bca-b711-cb2bb92bdfcb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant