Skip to content

fix: trap focus inside Dialog, AlertDialog, Sheet and Drawer - #64

Merged
Shewart merged 4 commits into
mainfrom
fix/overlay-focus-trap
Oct 18, 2026
Merged

Shewart merged 4 commits into
mainfrom
fix/overlay-focus-trap

Conversation

@Shewart

@Shewart Shewart commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #50.

Merge after the template drift PR. This branch is stacked on fix/template-drift-1, because the legacy Sheet and Drawer changes touch the same lines. Until that merges, this PR also shows its eight commits; the last three are this fix.

With a Dialog, AlertDialog, Sheet or Drawer open, Tab and Shift+Tab now stay inside it, and focus returns to the element that opened it when it closes.

  • shellui.js (package and CLI template, identical): trapFocus(handle, element) and releaseFocus(handle). Same pattern as the scroll lock: a stack, so a dialog opened from a dialog traps the top one; a capture-phase Tab handler wraps at the first and last focusable elements; release restores focus to whatever had it before the overlay opened.
  • DialogContent, AlertDialogContent, SheetContent, DrawerContent (package and templates): trap on open, release on close and on dispose. They already focused the panel and closed on Escape.
  • Legacy Sheet and Drawer panels (Title / Description mode): previously took no focus at all. Now role="dialog", aria-modal, focus on open, the trap, and Escape to close.
  • If an existing CLI project updates a component but not shellui.js, the calls fail silently and the overlay behaves as before.

Checked: build 0 warnings, 352 tests (3 new). In a browser, in a scratch app using the package, with real Tab presses: all six overlays keep focus inside and wrap both ways, Escape returns focus to the trigger, no traps left registered afterwards, no console errors. Not checked: a dialog opened from inside another dialog, and the CLI install path in a browser.

Copilot AI balanced review requested due to automatic review settings October 17, 2026 20:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@Shewart
Shewart merged commit ec1d6f6 into main Oct 18, 2026
1 check passed
@Shewart
Shewart deleted the fix/overlay-focus-trap branch October 18, 2026 10:43
Shewart added a commit that referenced this pull request Oct 20, 2026
Release notes and version references for 0.4.0-alpha.3. The first
release published with NuGet Trusted Publishing (#71).

**In this release** (already on main)
- #69: a component's `Class` overrides its own conflicting classes
(`Shell.Cn` merges like tailwind-merge)
- #64: Dialog, AlertDialog, Sheet and Drawer keep keyboard focus inside
and return it on close
- #70: `add` never downgrades a NuGet package the project already
references
- #71: `init` supports Razor class libraries, plus a guide; release
workflow on Trusted Publishing
- #63, #65, #66, #68: CLI templates reconciled with the package
(RadioGroup, ScrollArea, DatePicker, TimePicker, Pagination, Toast,
Skeleton, Sheet/Drawer and more)

**Breaking for CLI template users** (after `shellui update`, all in the
notes with upgrade steps): `Input.InputVariant` becomes `InputVariant`
(new `input-variants` template); Stepper parts read `ActiveStep` instead
of taking `CurrentStep`; `ScrollArea` drops its `scrollbar-*` classes.
`update` installs the new templates automatically.

**This PR**
- `Directory.Build.props` suffix `alpha.3`; README, VERSIONING_STRATEGY,
CLI_INSTALLATION, CLI_SYNTAX, FAQ, QUICKSTART and the version fallbacks
move to `0.4.0-alpha.3`
- `docs/RELEASE_NOTES.md`: the 0.4.0-alpha.3 section;
`scripts/extract-release-notes.sh 0.4.0-alpha.3` extracts it

**Release checks**
- Release build has 0 warnings, and all 484 tests pass
- An app set up with the released 0.4.0-alpha.2 CLI, updated with the
packed alpha.3 CLI: 36 components updated, `input-variants`,
`textarea-variants` and `stepper-models` installed, builds 0/0
- CLI templates in a browser: RadioGroup selects (disabled items
ignored), ScrollArea scrolls, Pagination `@bind-CurrentPage`, Stepper
Next, DatePicker puts 1 October 2026 under Thursday, TimePicker
`Step="15"` lists 00/15/30/45, `Class="px-8 bg-destructive"` replaces
the button's own `px-4 bg-primary`, `InputVariant.Error` applies
- Focus trap with real key presses (templates and package): Tab cycles
inside the dialog and wraps, Shift+Tab goes back, Escape closes and
returns focus to the trigger
- All 93 direct targets together in `my-app` build 0/0 on net8.0, net9.0
and net10.0 (244 registry entries)
- The packed `ShellUI.Components` in a browser: the class override,
TimePicker, dark mode with `color-scheme`, Popover Escape, Command
`Action`, the dialog focus trap; no console errors
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.

Overlays don't trap focus

2 participants