diff --git a/packages/edit-site/CHANGELOG.md b/packages/edit-site/CHANGELOG.md
index 27e12aef878f8d..f5c98f6aea0285 100644
--- a/packages/edit-site/CHANGELOG.md
+++ b/packages/edit-site/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### Code Quality
+
+- Migrate the registered-template duplicate dialog in Page Templates from `@wordpress/components` `Modal` to `@wordpress/ui` `Dialog`. ([#76837](https://github.com/WordPress/gutenberg/pull/76837))
+
## 6.45.0 (2026-04-29)
## 6.44.0 (2026-04-15)
diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js
index e019bf290b39dd..a48885806e4514 100644
--- a/packages/edit-site/src/components/page-templates/index.js
+++ b/packages/edit-site/src/components/page-templates/index.js
@@ -16,7 +16,8 @@ import { addQueryArgs } from '@wordpress/url';
import { useSelect, useDispatch } from '@wordpress/data';
import { useEvent } from '@wordpress/compose';
import { useView } from '@wordpress/views';
-import { Modal } from '@wordpress/components';
+// eslint-disable-next-line @wordpress/use-recommended-components
+import { Dialog } from '@wordpress/ui';
import { store as noticesStore } from '@wordpress/notices';
/**
@@ -54,6 +55,7 @@ export default function PageTemplates() {
const [ selection, setSelection ] = useState( [ postId ] );
const [ selectedRegisteredTemplate, setSelectedRegisteredTemplate ] =
useState( false );
+ const [ isDuplicateOpen, setIsDuplicateOpen ] = useState( false );
const defaultView = DEFAULT_VIEW;
const activeViewOverrides = useMemo(
() => getActiveViewOverridesForTab( activeView ),
@@ -342,6 +344,7 @@ export default function PageTemplates() {
onClickItem={ ( item ) => {
if ( typeof item.id === 'string' ) {
setSelectedRegisteredTemplate( item );
+ setIsDuplicateOpen( true );
} else {
history.navigate(
`/${ item.type }/${ item.id }?canvas=edit`
@@ -359,17 +362,50 @@ export default function PageTemplates() {
: false
}
/>
- { selectedRegisteredTemplate && duplicateAction && (
- setSelectedRegisteredTemplate() }
- size="small"
+ { duplicateAction && (
+ {
+ if ( ! isOpen ) {
+ // Clear the cached template only after the exit
+ // animation finishes — keeps the modal contents
+ // rendered while the dialog slides out so the
+ // inner DataForm doesn't flash empty. The
+ // functional setter guards against a stale
+ // "complete" callback firing after the user has
+ // already re-opened the dialog with a different
+ // template (mid-animation re-open race).
+ setSelectedRegisteredTemplate( ( prev ) =>
+ isDuplicateOpen ? prev : false
+ );
+ }
+ } }
>
- setSelectedRegisteredTemplate() }
- />
-
+
+
+ { __( 'Duplicate' ) }
+
+
+
+ { selectedRegisteredTemplate && (
+ // Keying on the template id ensures the
+ // RenderModal remounts with a fresh internal
+ // `useState` initializer if the user clicks a
+ // different row while the dialog is still
+ // closing — otherwise the previous template's
+ // title would persist into the new session.
+
+ setIsDuplicateOpen( false )
+ }
+ />
+ ) }
+
+
+
) }
);