diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index 311c5d7fae495a..cd89b9461d07e8 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -429,6 +429,16 @@ Add custom HTML code and preview it as you edit. ([Source](https://github.com/Wo
- **Supports:** interactivity (clientNavigation), ~~className~~, ~~customCSS~~, ~~customClassName~~, ~~html~~
- **Attributes:** content
+## Icon
+
+Insert an SVG icon. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/icon))
+
+- **Name:** core/icon
+- **Experimental:** true
+- **Category:** media
+- **Supports:** align (center, left, right), anchor, ariaLabel, color (background, text), dimensions (width), interactivity (clientNavigation), spacing (margin, padding), ~~html~~
+- **Attributes:** icon, style
+
## Image
Insert an image to make a visual statement. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/image))
@@ -528,7 +538,7 @@ A collection of blocks that allow visitors to get around your site. ([Source](ht
- **Name:** core/navigation
- **Category:** theme
-- **Allowed Blocks:** core/navigation-link, core/search, core/social-links, core/page-list, core/spacer, core/home-link, core/site-title, core/site-logo, core/navigation-submenu, core/loginout, core/buttons
+- **Allowed Blocks:** core/navigation-link, core/search, core/social-links, core/page-list, core/spacer, core/home-link, core/icon, core/site-title, core/site-logo, core/navigation-submenu, core/loginout, core/buttons
- **Supports:** align (full, wide), anchor, ariaLabel, contentRole, inserter, interactivity, layout (allowSizingOnChildren, default, ~~allowInheriting~~, ~~allowSwitching~~, ~~allowVerticalAlignment~~), spacing (blockGap, units), typography (fontSize, lineHeight), ~~html~~, ~~renaming~~
- **Attributes:** __unstableLocation, backgroundColor, customBackgroundColor, customOverlayBackgroundColor, customOverlayTextColor, customTextColor, hasIcon, icon, maxNestingLevel, overlay, overlayBackgroundColor, overlayMenu, overlayTextColor, ref, rgbBackgroundColor, rgbTextColor, showSubmenuIcon, submenuVisibility, templateLock, textColor
diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php
index 9c90acc005c81d..6be9aab1925a98 100644
--- a/lib/experimental/editor-settings.php
+++ b/lib/experimental/editor-settings.php
@@ -69,6 +69,11 @@ function gutenberg_enable_block_experiments() {
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-experiments', $gutenberg_experiments ) ) {
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableBlockExperiments = true', 'before' );
}
+
+ // Adding explicit support for the Icon block if the SVG icon registry is active.
+ if ( $gutenberg_experiments && array_key_exists( 'gutenberg-svg-icon-registry', $gutenberg_experiments ) ) {
+ wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableIconBlock = true', 'before' );
+ }
}
add_action( 'admin_init', 'gutenberg_enable_block_experiments' );
diff --git a/lib/experiments-page.php b/lib/experiments-page.php
index 2d4149014e627f..1fee6ded0e5389 100644
--- a/lib/experiments-page.php
+++ b/lib/experiments-page.php
@@ -228,7 +228,7 @@ function gutenberg_initialize_experiments_settings() {
'gutenberg-experiments',
'gutenberg_experiments_section',
array(
- 'label' => __( 'Enables a REST API endpoint for accessing SVG icons.', 'gutenberg' ),
+ 'label' => __( 'Enables a REST API endpoint for accessing SVG icons as well as the associated Icon block.', 'gutenberg' ),
'id' => 'gutenberg-svg-icon-registry',
)
);
diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss
index 88ad6c2943167e..fd2f4b4859d3f9 100644
--- a/packages/block-library/src/editor.scss
+++ b/packages/block-library/src/editor.scss
@@ -22,6 +22,7 @@
@use "./gallery/editor.scss" as *;
@use "./group/editor.scss" as *;
@use "./html/editor.scss" as *;
+@use "./icon/editor.scss" as *;
@use "./image/editor.scss" as *;
@use "./latest-posts/editor.scss" as *;
@use "./math/editor.scss" as *;
diff --git a/packages/block-library/src/icon/block.json b/packages/block-library/src/icon/block.json
new file mode 100644
index 00000000000000..2916fd653987ee
--- /dev/null
+++ b/packages/block-library/src/icon/block.json
@@ -0,0 +1,65 @@
+{
+ "apiVersion": 3,
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "name": "core/icon",
+ "title": "Icon",
+ "__experimental": true,
+ "category": "media",
+ "description": "Insert an SVG icon.",
+ "keywords": [ "icon", "svg" ],
+ "textdomain": "default",
+ "attributes": {
+ "icon": {
+ "type": "string",
+ "role": "content"
+ },
+ "style": {
+ "type": "object",
+ "default": {
+ "dimensions": {
+ "width": "24px"
+ }
+ }
+ }
+ },
+ "supports": {
+ "anchor": true,
+ "ariaLabel": {
+ "__experimentalSkipSerialization": true
+ },
+ "align": [ "left", "center", "right" ],
+ "html": false,
+ "color": {
+ "background": true,
+ "text": true
+ },
+ "interactivity": {
+ "clientNavigation": true
+ },
+ "__experimentalBorder": {
+ "color": true,
+ "radius": true,
+ "style": true,
+ "width": true,
+ "__experimentalDefaultControls": {
+ "color": false,
+ "radius": false,
+ "style": false,
+ "width": false
+ }
+ },
+ "spacing": {
+ "padding": true,
+ "margin": true,
+ "__experimentalDefaultControls": {
+ "margin": false,
+ "padding": false
+ }
+ },
+ "dimensions": {
+ "width": true
+ }
+ },
+ "style": "wp-block-icon",
+ "editorStyle": "wp-block-icon-editor"
+}
diff --git a/packages/block-library/src/icon/components/custom-inserter/icon-grid.js b/packages/block-library/src/icon/components/custom-inserter/icon-grid.js
new file mode 100644
index 00000000000000..b5e51c247a428f
--- /dev/null
+++ b/packages/block-library/src/icon/components/custom-inserter/icon-grid.js
@@ -0,0 +1,55 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { Button } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import HtmlRenderer from '../../../utils/html-renderer';
+
+export default function IconGrid( { icons, onChange, attributes } ) {
+ return (
+
+ { icons.length === 0 ? (
+
+
{ __( 'No results found.' ) }
+
+ ) : (
+
+ { icons.map( ( icon ) => {
+ return (
+
+ );
+ } ) }
+
+ ) }
+
+ );
+}
diff --git a/packages/block-library/src/icon/components/custom-inserter/index.js b/packages/block-library/src/icon/components/custom-inserter/index.js
new file mode 100644
index 00000000000000..95473de42aee2b
--- /dev/null
+++ b/packages/block-library/src/icon/components/custom-inserter/index.js
@@ -0,0 +1,73 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { Modal, SearchControl } from '@wordpress/components';
+import { useState, useMemo, useCallback } from '@wordpress/element';
+import { useDebounce } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import IconGrid from './icon-grid';
+import { normalizeSearchInput } from '../../../utils/search-patterns';
+
+export default function CustomInserterModal( {
+ icons,
+ setInserterOpen,
+ attributes,
+ setAttributes,
+} ) {
+ const [ searchInput, setSearchInput ] = useState( '' );
+
+ const debouncedSetSearchInput = useDebounce( setSearchInput, 300 );
+
+ const setIcon = useCallback(
+ ( name ) => {
+ setAttributes( {
+ icon: name,
+ } );
+ setInserterOpen( false );
+ },
+ [ setAttributes, setInserterOpen ]
+ );
+
+ const filteredIcons = useMemo( () => {
+ if ( searchInput ) {
+ const input = normalizeSearchInput( searchInput );
+ return icons.filter( ( icon ) => {
+ const iconName = normalizeSearchInput( icon.name );
+ const iconLabel = normalizeSearchInput( icon.label );
+
+ return (
+ iconName.includes( input ) || iconLabel.includes( input )
+ );
+ } );
+ }
+
+ return icons;
+ }, [ searchInput, icons ] );
+
+ return (
+ setInserterOpen( false ) }
+ isFullScreen
+ >
+
+
+ );
+}
diff --git a/packages/block-library/src/icon/components/index.js b/packages/block-library/src/icon/components/index.js
new file mode 100644
index 00000000000000..2a53f2d299b798
--- /dev/null
+++ b/packages/block-library/src/icon/components/index.js
@@ -0,0 +1 @@
+export { default as CustomInserterModal } from './custom-inserter';
diff --git a/packages/block-library/src/icon/edit.js b/packages/block-library/src/icon/edit.js
new file mode 100644
index 00000000000000..03e39c8ddd7380
--- /dev/null
+++ b/packages/block-library/src/icon/edit.js
@@ -0,0 +1,187 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import {
+ DropdownMenu,
+ TextControl,
+ ToolbarButton,
+ ToolbarGroup,
+ __experimentalToolsPanel as ToolsPanel,
+ __experimentalToolsPanelItem as ToolsPanelItem,
+ Placeholder,
+} from '@wordpress/components';
+import {
+ BlockControls,
+ InspectorControls,
+ useBlockProps,
+ useBlockEditingMode,
+ store as blockEditorStore,
+} from '@wordpress/block-editor';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { store as coreDataStore } from '@wordpress/core-data';
+import { useState, useEffect } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
+import HtmlRenderer from '../utils/html-renderer';
+import { CustomInserterModal } from './components';
+import { unlock } from '../lock-unlock';
+
+export function Edit( { attributes, setAttributes } ) {
+ const { icon, ariaLabel, style } = attributes;
+
+ const [ isInserterOpen, setInserterOpen ] = useState( false );
+
+ const { __unstableMarkNextChangeAsNotPersistent } =
+ useDispatch( blockEditorStore );
+
+ const isContentOnlyMode = useBlockEditingMode() === 'contentOnly';
+
+ const allIcons = useSelect( ( select ) => {
+ return unlock( select( coreDataStore ) ).getIcons();
+ }, [] );
+
+ // Is the width value is 0, reset it to the default value.
+ useEffect( () => {
+ if (
+ ! style?.dimensions?.width ||
+ parseFloat( style?.dimensions?.width ) === 0
+ ) {
+ // To avoid interfering with undo/redo operations any changes in this
+ // effect must not make history and should be preceded by
+ // `__unstableMarkNextChangeAsNotPersistent()`.
+ __unstableMarkNextChangeAsNotPersistent();
+ setAttributes( {
+ style: {
+ ...style,
+ dimensions: { ...style?.dimensions, width: '12px' },
+ },
+ } );
+ }
+ }, [
+ icon,
+ style,
+ setAttributes,
+ __unstableMarkNextChangeAsNotPersistent,
+ ] );
+
+ const iconToDisplay =
+ allIcons?.length > 0
+ ? allIcons?.find( ( { name } ) => name === icon )?.content
+ : '';
+
+ const blockControls = (
+ <>
+
+ {
+ setInserterOpen( true );
+ } }
+ >
+ { icon ? __( 'Replace' ) : __( 'Choose icon' ) }
+
+
+ { isContentOnlyMode && icon && (
+ // Add some extra controls for content attributes when content only mode is active.
+ // With content only mode active, the inspector is hidden, so users need another way
+ // to edit these attributes.
+
+
+
+ { () => (
+
+ setAttributes( { ariaLabel: value } )
+ }
+ help={ __(
+ 'Briefly describe the icon to help screen reader users. Leave blank for decorative icons.'
+ ) }
+ __next40pxDefaultSize
+ />
+ ) }
+
+
+
+ ) }
+ >
+ );
+ const dropdownMenuProps = useToolsPanelDropdownMenuProps();
+ const inspectorControls = icon && (
+ <>
+
+
+ setAttributes( {
+ ariaLabel: undefined,
+ } )
+ }
+ dropdownMenuProps={ dropdownMenuProps }
+ >
+ !! ariaLabel }
+ onDeselect={ () =>
+ setAttributes( { ariaLabel: undefined } )
+ }
+ >
+
+ setAttributes( { ariaLabel: value } )
+ }
+ __next40pxDefaultSize
+ />
+
+
+
+ >
+ );
+
+ return (
+ <>
+ { blockControls }
+ { inspectorControls }
+
+ { icon ? (
+
+ ) : (
+
+ ) }
+
+ { isInserterOpen && (
+
+ ) }
+ >
+ );
+}
+
+export default Edit;
diff --git a/packages/block-library/src/icon/editor.scss b/packages/block-library/src/icon/editor.scss
new file mode 100644
index 00000000000000..1ca78c6a46d6bd
--- /dev/null
+++ b/packages/block-library/src/icon/editor.scss
@@ -0,0 +1,57 @@
+@use "@wordpress/base-styles/variables" as *;
+@use "@wordpress/base-styles/colors" as *;
+
+// Style for the icon library modal.
+.wp-block-icon__inserter {
+ padding: 0 $grid-unit-30;
+ margin: 0 (-1 * $grid-unit-30);
+}
+
+.wp-block-icon__inserter-header {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: $grid-unit-20;
+}
+
+.wp-block-icon__inserter-grid-icons-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+}
+
+.wp-block-icon__inserter-grid-no-results {
+ display: flex;
+ justify-content: center;
+}
+
+.wp-block-icon__inserter-grid-icons-list-item {
+ display: flex;
+ flex-direction: column;
+ height: auto !important;
+}
+
+.wp-block-icon__inserter-grid-icons-list-item-icon {
+ padding: $grid-unit-15;
+}
+
+.wp-block-icon__inserter-grid-icons-list-item-title {
+ font-size: $font-size-small;
+ padding: $grid-unit-05 $grid-unit-05 $grid-unit-10;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.wp-block-icon__toolbar-content {
+ width: 250px;
+}
+
+// Provide special styling for the placeholder.
+// @todo this particular minimal style of placeholder could be componentized further.
+.wp-block-icon .components-placeholder {
+ padding: 0;
+ min-height: $grid-unit-30;
+ min-width: $grid-unit-30;
+ height: 100%;
+ width: 100%;
+}
diff --git a/packages/block-library/src/icon/icon.js b/packages/block-library/src/icon/icon.js
new file mode 100644
index 00000000000000..019de4281a5294
--- /dev/null
+++ b/packages/block-library/src/icon/icon.js
@@ -0,0 +1,14 @@
+/**
+ * WordPress dependencies
+ */
+import { SVG, Path } from '@wordpress/components';
+
+/**
+ * SVG to represent the block.
+ */
+export const icon = (
+
+);
+export default icon;
diff --git a/packages/block-library/src/icon/index.js b/packages/block-library/src/icon/index.js
new file mode 100644
index 00000000000000..d83f59c00670b5
--- /dev/null
+++ b/packages/block-library/src/icon/index.js
@@ -0,0 +1,26 @@
+/**
+ * Internal dependencies
+ */
+import initBlock from '../utils/init-block';
+import edit from './edit';
+import metadata from './block.json';
+import icon from './icon';
+
+const { name } = metadata;
+export { metadata, name };
+export const settings = {
+ icon,
+ example: {
+ attributes: {
+ icon: 'core/audio',
+ style: {
+ dimensions: {
+ width: '48px',
+ },
+ },
+ },
+ },
+ edit,
+};
+
+export const init = () => initBlock( { name, metadata, settings } );
diff --git a/packages/block-library/src/icon/index.php b/packages/block-library/src/icon/index.php
new file mode 100644
index 00000000000000..e1cf2815ef8656
--- /dev/null
+++ b/packages/block-library/src/icon/index.php
@@ -0,0 +1,65 @@
+get_registered_icon( $attributes['icon'] );
+
+ if ( is_null( $icon ) ) {
+ return;
+ }
+
+ $aria_label = ! empty( $attributes['ariaLabel'] ) ? $attributes['ariaLabel'] : '';
+
+ // Process the markup.
+ $processor = new WP_HTML_Tag_Processor( $icon['content'] );
+ $processor->next_tag( 'svg' );
+ if ( ! $aria_label ) {
+ // Icon is decorative, hide it from screen readers.
+ $processor->set_attribute( 'aria-hidden', 'true' );
+ $processor->set_attribute( 'focusable', 'false' );
+ } else {
+ $processor->set_attribute( 'role', 'img' );
+ $processor->set_attribute( 'aria-label', $aria_label );
+ }
+
+ // Return the updated SVG markup.
+ $svg = $processor->get_updated_html();
+ $attributes = get_block_wrapper_attributes();
+ return sprintf( '%s
', $attributes, $svg );
+}
+
+
+/**
+ * Registers the `core/icon` block on server.
+ *
+ * @since 7.0.0
+ */
+function register_block_core_icon() {
+ register_block_type_from_metadata(
+ __DIR__ . '/icon',
+ array(
+ 'render_callback' => 'render_block_core_icon',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_icon' );
diff --git a/packages/block-library/src/icon/style.scss b/packages/block-library/src/icon/style.scss
new file mode 100644
index 00000000000000..200b5b479277ef
--- /dev/null
+++ b/packages/block-library/src/icon/style.scss
@@ -0,0 +1,13 @@
+/**
+ * Editor and frontend styles for the Icon Block.
+ */
+
+/* Icon Block styles. */
+.wp-block-icon {
+ line-height: 0;
+ svg {
+ width: 100%;
+ height: 100%;
+ fill: currentColor;
+ }
+}
diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js
index f891ec38c0aa2d..65e685cd6059d4 100644
--- a/packages/block-library/src/index.js
+++ b/packages/block-library/src/index.js
@@ -74,6 +74,7 @@ import * as group from './group';
import * as heading from './heading';
import * as homeLink from './home-link';
import * as html from './html';
+import * as icon from './icon';
import * as image from './image';
import * as latestComments from './latest-comments';
import * as latestPosts from './latest-posts';
@@ -297,6 +298,10 @@ const getAllBlocks = () => {
blocks.push( playlistTrack );
}
+ if ( window?.__experimentalEnableIconBlock ) {
+ blocks.push( icon );
+ }
+
// When in a WordPress context, conditionally
// add the classic block and TinyMCE editor
// under any of the following conditions:
diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json
index 78941f9c3b575f..c13ac0000f34df 100644
--- a/packages/block-library/src/navigation/block.json
+++ b/packages/block-library/src/navigation/block.json
@@ -11,6 +11,7 @@
"core/page-list",
"core/spacer",
"core/home-link",
+ "core/icon",
"core/site-title",
"core/site-logo",
"core/navigation-submenu",
diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss
index 1aac2fc64e05ca..f1c652d56b2dce 100644
--- a/packages/block-library/src/style.scss
+++ b/packages/block-library/src/style.scss
@@ -28,6 +28,7 @@
@use "./gallery/style.scss" as *;
@use "./group/style.scss" as *;
@use "./heading/style.scss" as *;
+@use "./icon/style.scss" as *;
@use "./image/style.scss" as *;
@use "./latest-comments/style.scss" as *;
@use "./latest-posts/style.scss" as *;
diff --git a/packages/core-data/src/private-actions.js b/packages/core-data/src/private-actions.js
index 856062b46fd92e..27ceda015da374 100644
--- a/packages/core-data/src/private-actions.js
+++ b/packages/core-data/src/private-actions.js
@@ -160,3 +160,17 @@ export function receiveEditorAssets( assets ) {
assets,
};
}
+
+/**
+ * Returns an action object used to receive icons.
+ *
+ * @param {Array} icons List of icons.
+ *
+ * @return {Object} Action object.
+ */
+export function receiveIcons( icons ) {
+ return {
+ type: 'RECEIVE_ICONS',
+ icons,
+ };
+}
diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts
index 3c2a5446dda7c2..36517505274e42 100644
--- a/packages/core-data/src/private-selectors.ts
+++ b/packages/core-data/src/private-selectors.ts
@@ -6,7 +6,12 @@ import { createSelector, createRegistrySelector } from '@wordpress/data';
/**
* Internal dependencies
*/
-import { getDefaultTemplateId, getEntityRecord, type State } from './selectors';
+import {
+ getDefaultTemplateId,
+ getEntityRecord,
+ type State,
+ type Icon,
+} from './selectors';
import { STORE_NAME } from './name';
import { unlock } from './lock-unlock';
import { getSyncManager } from './sync';
@@ -308,3 +313,13 @@ export function getEditorSettings(
export function getEditorAssets( state: State ): Record< string, any > | null {
return state.editorAssets;
}
+
+/**
+ * Returns the list of available icons.
+ *
+ * @param state Data state.
+ * @return The list of icons or empty array if not loaded.
+ */
+export function getIcons( state: State ): Icon[] {
+ return state.icons ?? [];
+}
diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js
index f2acaa34fb8c60..dd6ffc177b07be 100644
--- a/packages/core-data/src/reducer.js
+++ b/packages/core-data/src/reducer.js
@@ -660,6 +660,22 @@ export function editorAssets( state = null, action ) {
return state;
}
+/**
+ * Reducer managing icons.
+ *
+ * @param {Array} state Current state.
+ * @param {Object} action Action object.
+ *
+ * @return {Array} Updated state.
+ */
+export function icons( state = [], action ) {
+ switch ( action.type ) {
+ case 'RECEIVE_ICONS':
+ return action.icons;
+ }
+ return state;
+}
+
export default combineReducers( {
users,
currentTheme,
@@ -682,4 +698,5 @@ export default combineReducers( {
registeredPostMeta,
editorSettings,
editorAssets,
+ icons,
} );
diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js
index bf77698208eda1..e7bc0f5173323b 100644
--- a/packages/core-data/src/resolvers.js
+++ b/packages/core-data/src/resolvers.js
@@ -1254,3 +1254,15 @@ export const getEditorAssets =
} );
dispatch.receiveEditorAssets( assets );
};
+
+/**
+ * Requests icons from the REST API.
+ */
+export const getIcons =
+ () =>
+ async ( { dispatch } ) => {
+ const icons = await apiFetch( {
+ path: '/wp/v2/icons',
+ } );
+ dispatch.receiveIcons( icons );
+ };
diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts
index f322e85326f73a..84ee439d995058 100644
--- a/packages/core-data/src/selectors.ts
+++ b/packages/core-data/src/selectors.ts
@@ -52,10 +52,17 @@ export interface State {
registeredPostMeta: Record< string, Object >;
editorSettings: Record< string, any > | null;
editorAssets: Record< string, any > | null;
+ icons: Icon[];
}
type EntityRecordKey = string | number;
+export interface Icon {
+ name: string;
+ content: string;
+ label: string;
+}
+
interface EntitiesState {
config: EntityConfig[];
records: Record< string, Record< string, EntityState< ET.EntityRecord > > >;
diff --git a/test/integration/fixtures/blocks/core__icon.html b/test/integration/fixtures/blocks/core__icon.html
new file mode 100644
index 00000000000000..c6c2c59ef044b4
--- /dev/null
+++ b/test/integration/fixtures/blocks/core__icon.html
@@ -0,0 +1 @@
+
diff --git a/test/integration/fixtures/blocks/core__icon.json b/test/integration/fixtures/blocks/core__icon.json
new file mode 100644
index 00000000000000..bf6d3621208325
--- /dev/null
+++ b/test/integration/fixtures/blocks/core__icon.json
@@ -0,0 +1,14 @@
+[
+ {
+ "name": "core/icon",
+ "isValid": true,
+ "attributes": {
+ "style": {
+ "dimensions": {
+ "width": "24px"
+ }
+ }
+ },
+ "innerBlocks": []
+ }
+]
diff --git a/test/integration/fixtures/blocks/core__icon.parsed.json b/test/integration/fixtures/blocks/core__icon.parsed.json
new file mode 100644
index 00000000000000..e02039a280361a
--- /dev/null
+++ b/test/integration/fixtures/blocks/core__icon.parsed.json
@@ -0,0 +1,9 @@
+[
+ {
+ "blockName": "core/icon",
+ "attrs": {},
+ "innerBlocks": [],
+ "innerHTML": "",
+ "innerContent": []
+ }
+]
diff --git a/test/integration/fixtures/blocks/core__icon.serialized.html b/test/integration/fixtures/blocks/core__icon.serialized.html
new file mode 100644
index 00000000000000..c6c2c59ef044b4
--- /dev/null
+++ b/test/integration/fixtures/blocks/core__icon.serialized.html
@@ -0,0 +1 @@
+
diff --git a/test/integration/full-content/full-content.test.js b/test/integration/full-content/full-content.test.js
index 471b8797bf9a21..7177b7048743e0 100644
--- a/test/integration/full-content/full-content.test.js
+++ b/test/integration/full-content/full-content.test.js
@@ -1,12 +1,13 @@
/**
* External dependencies
*/
-import glob from 'fast-glob';
import { format } from 'util';
+import glob from 'fast-glob';
/**
* WordPress dependencies
*/
+import prettierConfig from '@wordpress/prettier-config';
import {
getBlockTypes,
parse,
@@ -18,7 +19,6 @@ import {
registerCoreBlocks,
__experimentalRegisterExperimentalCoreBlocks,
} from '@wordpress/block-library';
-import prettierConfig from '@wordpress/prettier-config';
/**
* Internal dependencies
@@ -66,6 +66,9 @@ describe( 'full post content fixture', () => {
// Form-related blocks will not be registered unless they are opted
// in on the experimental settings page.
window.__experimentalEnableFormBlocks = true;
+ // Icon block will not be registered unless are opted into on the
+ // experimental settings page.
+ window.__experimentalEnableIconBlock = true;
registerCoreBlocks();
if ( globalThis.IS_GUTENBERG_PLUGIN ) {