From c056730a5882f0983af44080770cf6b8e3c22e90 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Jun 2026 15:40:59 +0000
Subject: [PATCH 1/4] Initial plan
From 276593387ec84f72aeba02a940b5e0a4d0a026a9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Jun 2026 15:47:42 +0000
Subject: [PATCH 2/4] Detach confirm() host element from body on close to fix
leak
Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
---
.changeset/confirm-dialog-host-cleanup.md | 5 ++++
.../ConfirmationDialog.test.tsx | 26 ++++++++++++++++++-
.../ConfirmationDialog/ConfirmationDialog.tsx | 6 ++---
3 files changed, 33 insertions(+), 4 deletions(-)
create mode 100644 .changeset/confirm-dialog-host-cleanup.md
diff --git a/.changeset/confirm-dialog-host-cleanup.md b/.changeset/confirm-dialog-host-cleanup.md
new file mode 100644
index 00000000000..27b2c8114e6
--- /dev/null
+++ b/.changeset/confirm-dialog-host-cleanup.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': patch
+---
+
+ConfirmationDialog: `useConfirm`/`confirm` now removes its host element from `document.body` after the dialog is closed, and uses a fresh host element per call, so the empty container no longer lingers or leaks into other components and tests
diff --git a/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx b/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx
index eeb91d7e9fd..bc43ee511c3 100644
--- a/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx
+++ b/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx
@@ -1,4 +1,4 @@
-import {render, fireEvent} from '@testing-library/react'
+import {render, fireEvent, waitFor} from '@testing-library/react'
import {describe, it, expect, vi} from 'vitest'
import type React from 'react'
import {useCallback, useRef, useState} from 'react'
@@ -310,4 +310,28 @@ describe('ConfirmationDialog', () => {
})
implementsClassName(ConfirmationDialog, dialogClasses.Dialog)
+
+ describe('useConfirm', () => {
+ it('removes the host element from the document body when the dialog is closed', async () => {
+ const initialBodyChildCount = document.body.childElementCount
+ const {getByText, getByRole} = render()
+
+ fireEvent.click(getByText('Show menu'))
+ fireEvent.click(getByText('Show dialog'))
+
+ // The dialog is rendered into a host element appended to
+ expect(getByRole('alertdialog')).toBeInTheDocument()
+ expect(document.body.childElementCount).toBeGreaterThan(initialBodyChildCount + 1)
+
+ fireEvent.click(getByRole('button', {name: 'Secondary'}))
+
+ // After closing, neither the dialog nor its host element should linger in the DOM
+ await waitFor(() => {
+ expect(document.querySelector('[role="alertdialog"]')).toBeNull()
+ })
+ // The host element appended for the dialog must be detached from , leaving
+ // only the testing-library render container behind.
+ expect(document.body.childElementCount).toBe(initialBodyChildCount + 1)
+ })
+ })
})
diff --git a/packages/react/src/ConfirmationDialog/ConfirmationDialog.tsx b/packages/react/src/ConfirmationDialog/ConfirmationDialog.tsx
index c13fe632cad..e6693771b5d 100644
--- a/packages/react/src/ConfirmationDialog/ConfirmationDialog.tsx
+++ b/packages/react/src/ConfirmationDialog/ConfirmationDialog.tsx
@@ -138,16 +138,16 @@ export const ConfirmationDialog: React.FC & {content: React.ReactNode}
async function confirm(options: ConfirmOptions): Promise {
const {content, ...confirmationDialogProps} = options
return new Promise(resolve => {
- hostElement ||= document.createElement('div')
- if (!hostElement.isConnected) document.body.append(hostElement)
+ const hostElement = document.createElement('div')
+ document.body.append(hostElement)
const root = createRoot(hostElement)
const onClose: ConfirmationDialogProps['onClose'] = gesture => {
root.unmount()
+ hostElement.remove()
if (gesture === 'confirm') {
resolve(true)
} else {
From 87ab9c38e7b666208dd866d25a0b8ec342bcd19a Mon Sep 17 00:00:00 2001
From: Matthew Costabile
Date: Tue, 9 Jun 2026 19:43:54 -0400
Subject: [PATCH 3/4] Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---
.../ConfirmationDialog.test.tsx | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx b/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx
index bc43ee511c3..1e0524228cb 100644
--- a/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx
+++ b/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx
@@ -313,25 +313,26 @@ describe('ConfirmationDialog', () => {
describe('useConfirm', () => {
it('removes the host element from the document body when the dialog is closed', async () => {
- const initialBodyChildCount = document.body.childElementCount
const {getByText, getByRole} = render()
fireEvent.click(getByText('Show menu'))
+
+ // Capture children after the menu opens so we can reliably detect the confirm() host element
+ const bodyChildrenBeforeDialog = Array.from(document.body.children)
+
fireEvent.click(getByText('Show dialog'))
- // The dialog is rendered into a host element appended to
expect(getByRole('alertdialog')).toBeInTheDocument()
- expect(document.body.childElementCount).toBeGreaterThan(initialBodyChildCount + 1)
+
+ const hostElement = Array.from(document.body.children).find(el => !bodyChildrenBeforeDialog.includes(el))
+ if (!hostElement) throw new Error('Expected confirm() to append a host element to ')
fireEvent.click(getByRole('button', {name: 'Secondary'}))
// After closing, neither the dialog nor its host element should linger in the DOM
await waitFor(() => {
expect(document.querySelector('[role="alertdialog"]')).toBeNull()
+ expect(hostElement).not.toBeConnected()
})
- // The host element appended for the dialog must be detached from , leaving
- // only the testing-library render container behind.
- expect(document.body.childElementCount).toBe(initialBodyChildCount + 1)
- })
})
})
From 56cd164bdf7033051aa64f4257b88668e2d2bc24 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 10 Jun 2026 08:55:19 +0000
Subject: [PATCH 4/4] Fix missing closing bracket and invalid matcher in
ConfirmationDialog test
Co-authored-by: siddharthkp <1863771+siddharthkp@users.noreply.github.com>
---
.../react/src/ConfirmationDialog/ConfirmationDialog.test.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx b/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx
index 1e0524228cb..4939c2a03ea 100644
--- a/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx
+++ b/packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx
@@ -332,7 +332,8 @@ describe('ConfirmationDialog', () => {
// After closing, neither the dialog nor its host element should linger in the DOM
await waitFor(() => {
expect(document.querySelector('[role="alertdialog"]')).toBeNull()
- expect(hostElement).not.toBeConnected()
+ expect(hostElement.isConnected).toBe(false)
})
+ })
})
})