Skip to content

Fix drawer backdrop exit transition on close - #43028

Open
xiao115255 wants to merge 1 commit into
twbs:mainfrom
xiao115255:claude/issue-43024
Open

xiao115255 wants to merge 1 commit into
twbs:mainfrom
xiao115255:claude/issue-43024

Conversation

@xiao115255

Copy link
Copy Markdown

What & Why

Fixes #43024.

A closing Drawer dropped its native ::backdrop instantly. DialogBase closes the <dialog> synchronously, and leaving the top layer is what removes ::backdrop, so the backdrop never got a chance to transition. Dialog avoids this by overriding _shouldDeferClose() to keep the dialog in the top layer until the exit transition ends; Drawer did not override it, and was also missing the CSS that goes with it.

This makes Drawer mirror Dialog:

  • js/src/drawer.ts — override _shouldDeferClose() (same as Dialog).
  • scss/_drawer.scss — qualify the open-state selectors with :not(.hiding) so the exit falls back to the off-screen placement transform while [open] is still present, and add the .hiding::backdrop exit state so the backdrop fades out alongside the panel.

Two supporting fixes in js/src/dialog-base.ts, both reachable only because the close is now deferred:

  • dispose() clears .hiding. _queueCallback() skips its completion callback once the instance is disposed (an SPA tearing the drawer down mid-navigation, or re-instantiating on the same element), which would strand the exit state. With [open]:not(.hiding) a stranded .hiding makes the next instance's drawer invisible while <html> stays scroll locked.
  • The hide completion callback calls _closeAndCleanup() unconditionally. If something else closes the dialog during the exit (a <form method="dialog"> submit, app code calling dialog.close()), the previous if (element.open) guard skipped the cleanup and left dialog-open on <html> permanently. _closeAndCleanup() is already documented as safe on an already-closed dialog.

Note that Drawer now matches Dialog's exit behavior exactly, including that the dialog stays modal (and keeps swallowing pointer input) until the exit transition finishes.

How Tested

  • npm run js-test-unit — 1355 tests pass across 33 files, coverage above the configured thresholds.
  • npm run js-typecheck, npm run js-lint, npm run css-lint-stylelint, npm run css-test (47 specs), npm run dist — all pass.
  • New unit tests: the drawer stays [open] and .hiding while the exit runs (fails without the _shouldDeferClose() override); instant drawers still close in the same task; dispose() mid-transition clears .hiding (fails without the base-class fix).
  • End-to-end in headless Chromium against the compiled dist/ build, using the reproducer from the issue with --drawer-backdrop-bg overridden to make the backdrop visible, sampling getComputedStyle(dialog, '::backdrop') on the close click:
    • before: open and :modal both false 2 ms after the click, one distinct backdrop sample, no ::backdrop transition events;
    • after: [open] and :modal hold for the whole exit, background-color and backdrop-filter fire transitionrun/transitionend on ::backdrop, the alpha steps 0.25 → 0.14 → 0.07 → … → ~0 (10 distinct samples), and close() runs at ~298 ms.

AI Disclosure

This PR was prepared with AI assistance (Claude Code), reviewed and submitted by @xiao115255.

Drawer closed the <dialog> synchronously, so it left the top layer before
the exit transition ran and the browser dropped its ::backdrop instantly.
Dialog already defers close() until the exit transition ends and carries
the styles that go with it; Drawer did neither.

- Override _shouldDeferClose() in Drawer, as Dialog does.
- Qualify the Drawer open-state selectors with :not(.hiding) so the exit
  falls back to the off-screen placement transform while [open] is still
  present, and add the .hiding::backdrop exit state.
- Clear .hiding in DialogBase.dispose() and always call _closeAndCleanup()
  in the hide completion callback: the deferred close makes both paths
  reachable, and a stranded .hiding or a skipped cleanup would leave the
  element invisible or the page scroll locked.

Fixes twbs#43024
@xiao115255
xiao115255 requested review from a team as code owners October 10, 2026 18:37
@julien-deramond julien-deramond added v6 v6, the main branch bug Something does not work as documented css Sass sources or compiled CSS js JavaScript or TypeScript sources and plugins labels Oct 10, 2026

This branch has not been deployed

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

Labels

bug Something does not work as documented css Sass sources or compiled CSS js JavaScript or TypeScript sources and plugins v6 v6, the main branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v6.0.0-alpha.1: Drawer backdrop has no transition on close

2 participants