diff --git a/packages/editor/README.md b/packages/editor/README.md
index 3119f3f289637a..935545afc54bde 100644
--- a/packages/editor/README.md
+++ b/packages/editor/README.md
@@ -478,6 +478,10 @@ Monitors local autosaves of a post in the editor. It uses several hooks and func
The module also checks for sessionStorage support and conditionally exports the `LocalAutosaveMonitor` component based on that.
+### MainElementWarnings
+
+Undocumented declaration.
+
### MediaPlaceholder
> **Deprecated** since 5.3, use `wp.blockEditor.MediaPlaceholder` instead.
diff --git a/packages/editor/src/components/editor-interface/index.js b/packages/editor/src/components/editor-interface/index.js
index 6f6ffbec7b9c32..ae2452e01879b1 100644
--- a/packages/editor/src/components/editor-interface/index.js
+++ b/packages/editor/src/components/editor-interface/index.js
@@ -26,6 +26,7 @@ import SavePublishPanels from '../save-publish-panels';
import TextEditor from '../text-editor';
import VisualEditor from '../visual-editor';
import EditorContentSlotFill from './content-slot-fill';
+import MainElementWarnings from '../main-element-warnings';
const interfaceLabels = {
/* translators: accessibility text for the editor top bar landmark region. */
@@ -139,7 +140,10 @@ export default function EditorInterface( {
content={
<>
{ ! isDistractionFree && ! isPreviewMode && (
-
+ <>
+
+
+ >
) }
diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js
index d940532be75a3d..4ab2295f97fc00 100644
--- a/packages/editor/src/components/index.js
+++ b/packages/editor/src/components/index.js
@@ -21,6 +21,7 @@ export { default as EntitiesSavedStates } from './entities-saved-states';
export { useIsDirty as useEntitiesSavedStatesIsDirty } from './entities-saved-states/hooks/use-is-dirty';
export { default as ErrorBoundary } from './error-boundary';
export { default as LocalAutosaveMonitor } from './local-autosave-monitor';
+export { default as MainElementWarnings } from './main-element-warnings';
export { default as PageAttributesCheck } from './page-attributes/check';
export { default as PageAttributesOrder } from './page-attributes/order';
export { default as PageAttributesPanel } from './page-attributes/panel';
diff --git a/packages/editor/src/components/main-element-warnings/index.js b/packages/editor/src/components/main-element-warnings/index.js
new file mode 100644
index 00000000000000..cc5bdf621076a5
--- /dev/null
+++ b/packages/editor/src/components/main-element-warnings/index.js
@@ -0,0 +1,57 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { useEffect } from '@wordpress/element';
+import { store as blockEditorStore } from '@wordpress/block-editor';
+import { store as noticesStore } from '@wordpress/notices';
+
+/**
+ * Internal dependencies
+ */
+import { store as editorStore } from '../../store';
+
+const checkMainTag = ( blocks, mainTagCount ) => {
+ blocks.forEach( ( block ) => {
+ if ( block.attributes.tagName === 'main' ) {
+ mainTagCount++;
+ }
+ // If the block has innerBlocks, call the function again.
+ if ( block.innerBlocks.length > 0 ) {
+ mainTagCount = checkMainTag( block.innerBlocks, mainTagCount );
+ }
+ } );
+
+ return mainTagCount;
+};
+
+export default function MainElementWarnings() {
+ const { type, blocks } = useSelect( ( select ) => {
+ const postType = select( editorStore ).getCurrentPostType();
+ const { getBlocks } = select( blockEditorStore );
+ return {
+ type: postType,
+ blocks: getBlocks(),
+ };
+ }, [] );
+
+ const { createWarningNotice, removeNotice } = useDispatch( noticesStore );
+
+ useEffect( () => {
+ if ( 'wp_template' === type ) {
+ const mainTagCount = checkMainTag( blocks, 0 );
+
+ removeNotice( 'edit-site-main-notice' );
+
+ if ( 0 === mainTagCount || 1 < mainTagCount ) {
+ createWarningNotice(
+ __( 'Your template should have exactly one main element.' ),
+ { id: 'edit-site-main-notice' }
+ );
+ }
+ }
+ }, [ type, blocks, createWarningNotice, removeNotice ] );
+
+ return null;
+}