Skip to content

fix(Dialog): close on first Escape press when close button is focused#7915

Merged
jonrohan merged 1 commit into
mainfrom
fix/dialog-escape-close-button-tooltip
Jun 2, 2026
Merged

fix(Dialog): close on first Escape press when close button is focused#7915
jonrohan merged 1 commit into
mainfrom
fix/dialog-escape-close-button-tooltip

Conversation

@jonrohan
Copy link
Copy Markdown
Member

@jonrohan jonrohan commented Jun 1, 2026

Closes https://github.com/github/primer/issues/2604

When a Dialog opens, focus moves to its close button. Because icon buttons now render a tooltip by default, the tooltip's own Escape handler (registered on document via useOnEscapePress) runs first and calls stopImmediatePropagation() + preventDefault() to dismiss the tooltip — swallowing the first Escape keypress so the dialog doesn't close. Users had to press Escape twice (the previous test even worked around this), and in a multi-dialog setup the first Escape could shift focus to the wrong return-focus target instead of closing the dialog.

Fix

Intercept the Escape keydown on the close button itself. React dispatches onKeyDown at the root container, which sits below document in the DOM, so calling stopPropagation() there prevents the event from reaching the tooltip's document-level handler. We close the dialog directly with the 'escape' gesture, and the tooltip stays fully functional.

Considered but rejected: disabling the tooltip

The simplest fix was to set unsafeDisableTooltip on the close button. We chose not to do this because the tooltip contributes to the button's discoverability/affordance and we didn't want to regress the accessibility/UX of the close button just to work around the event handling. Intercepting the event keeps the tooltip — and its behavior — intact while fixing the bug.

Changelog

Changed

  • Dialog now closes on the first Escape keypress when the close button is focused, instead of requiring a second press.

Rollout strategy

  • Patch release

Testing & Reviewing

  1. Render two buttons that each open their own Dialog (each with a returnFocusRef).
  2. Open the first dialog and press Escape once.
  3. The dialog should close on the first press and focus should return to its own trigger button.

Added a regression test in Dialog.test.tsx covering the single-Escape close and the multi-dialog scenario, and updated the existing Escape test to expect a single keypress. All 40 Dialog tests pass; type-check is clean.

Merge checklist

  • Added/updated tests
  • Added/updated documentation
  • Added/updated previews (Storybook)
  • Changes are SSR compatible
  • Tested in Chrome
  • Tested in Firefox
  • Tested in Safari
  • Tested in Edge
  • (GitHub staff only) Integration tests pass at github/github-ui

The close button's tooltip registered a document-level Escape handler that swallowed the first keypress. Intercept Escape on the close button and stop propagation so the dialog closes immediately while keeping the tooltip functional.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 1, 2026

🦋 Changeset detected

Latest commit: daa8dbe

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added staff Author is a staff member integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm labels Jun 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

⚠️ Action required

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label to this PR.

@jonrohan jonrohan marked this pull request as ready for review June 1, 2026 21:44
@jonrohan jonrohan requested a review from a team as a code owner June 1, 2026 21:44
@jonrohan jonrohan requested review from Copilot and siddharthkp June 1, 2026 21:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a regression where Dialog required two Escape key presses to close when the close button is focused, due to the close button tooltip’s document-level Escape handler preventing the dialog’s own Escape handler from running.

Changes:

  • Add an onKeyDown handler to Dialog.CloseButton to intercept Escape, stop propagation, and close the dialog immediately.
  • Update Dialog Escape tests and add a regression test for the multiple-dialogs scenario.
  • Add a patch changeset documenting the behavior change.
Show a summary per file
File Description
packages/react/src/Dialog/Dialog.tsx Intercepts Escape on the close button to avoid the tooltip swallowing the first keypress and closes the dialog immediately.
packages/react/src/Dialog/Dialog.test.tsx Updates Escape-close expectations and adds a multi-dialog regression test.
.changeset/dialog-escape-close-button-tooltip.md Patch changeset describing the fixed Escape-close behavior.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment on lines +231 to +233
event.stopPropagation()
onClose('escape')
}
Comment on lines +213 to +225
const {getByText} = render(
<>
<ButtonWithDialog label="Dialog 1" onClose={onCloseFirst} />
<ButtonWithDialog label="Dialog 2" onClose={onCloseSecond} />
</>,
)

await user.click(getByText('Dialog 1'))
await user.keyboard('{Escape}')

expect(onCloseFirst).toHaveBeenCalled()
expect(onCloseSecond).not.toHaveBeenCalled()
})
@jonrohan jonrohan added the Canary Release Apply this label when you want CI to create a canary release of the current PR label Jun 1, 2026
@primer-integration
Copy link
Copy Markdown

Integration test results from github/github-ui PR:

Passed  CI   Passed
Passed  VRT   Passed
Passed  Projects   Passed

All checks passed!

Copy link
Copy Markdown
Contributor

@llastflowers llastflowers left a comment

Choose a reason for hiding this comment

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

Tested in Storybook, looks great! ✨

@jonrohan jonrohan added this pull request to the merge queue Jun 2, 2026
Merged via the queue into main with commit f58e448 Jun 2, 2026
62 checks passed
@jonrohan jonrohan deleted the fix/dialog-escape-close-button-tooltip branch June 2, 2026 21:10
@primer primer Bot mentioned this pull request Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Canary Release Apply this label when you want CI to create a canary release of the current PR integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm staff Author is a staff member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants