diff --git a/blocks/README.md b/blocks/README.md
index 77a7624ed0a067..5762963145c15b 100644
--- a/blocks/README.md
+++ b/blocks/README.md
@@ -232,10 +232,13 @@ editor interface where blocks are implemented.
- `title: string` - A human-readable
[localized](https://codex.wordpress.org/I18n_for_WordPress_Developers#Handling_JavaScript_files)
label for the block. Shown in the block inserter.
-- `icon: string | WPElement | Function` - Slug of the
+- `icon: string | WPElement | Function | Object` - Slug of the
[Dashicon](https://developer.wordpress.org/resource/dashicons/#awards)
to be shown in the control's button, or an element (or function returning an
element) if you choose to render your own SVG.
+ An object can also be passed, in this case, icon, as specified above, should be included in the src property.
+ Besides src the object can contain background and foreground colors, this colors will appear with the icon
+ when they are applicable e.g.: in the inserter.
- `attributes: Object | Function` - An object of attribute schemas, where the
keys of the object define the shape of attributes, and each value an object
schema describing the `type`, `default` (optional), and
diff --git a/blocks/api/registration.js b/blocks/api/registration.js
index 67d21385a74dff..66b172a7a6c23b 100644
--- a/blocks/api/registration.js
+++ b/blocks/api/registration.js
@@ -12,29 +12,34 @@ import { applyFilters } from '@wordpress/hooks';
import { select, dispatch } from '@wordpress/data';
import deprecated from '@wordpress/deprecated';
+/**
+ * Internal dependencies
+ */
+import { normalizeIconObject } from './utils';
+
/**
* Defined behavior of a block type.
*
* @typedef {WPBlockType}
*
- * @property {string} name Block's namespaced name.
- * @property {string} title Human-readable label for a block.
- * Shown in the block inserter.
- * @property {string} category Category classification of block,
- * impacting where block is shown in
- * inserter results.
- * @property {(string|WPElement)} icon Slug of the Dashicon to be shown
- * as the icon for the block in the
- * inserter, or element.
- * @property {?string[]} keywords Additional keywords to produce
- * block as inserter search result.
- * @property {?Object} attributes Block attributes.
- * @property {Function} save Serialize behavior of a block,
- * returning an element describing
- * structure of the block's post
- * content markup.
- * @property {WPComponent} edit Component rendering element to be
- * interacted with in an editor.
+ * @property {string} name Block's namespaced name.
+ * @property {string} title Human-readable label for a block.
+ * Shown in the block inserter.
+ * @property {string} category Category classification of block,
+ * impacting where block is shown in
+ * inserter results.
+ * @property {(Object|string|WPElement)} icon Slug of the Dashicon to be shown
+ * as the icon for the block in the
+ * inserter, or element or an object describing the icon.
+ * @property {?string[]} keywords Additional keywords to produce
+ * block as inserter search result.
+ * @property {?Object} attributes Block attributes.
+ * @property {Function} save Serialize behavior of a block,
+ * returning an element describing
+ * structure of the block's post
+ * content markup.
+ * @property {WPComponent} edit Component rendering element to be
+ * interacted with in an editor.
*/
/**
@@ -134,9 +139,9 @@ export function registerBlockType( name, settings ) {
);
return;
}
- if ( ! settings.icon ) {
- settings.icon = 'block-default';
- }
+
+ settings.icon = normalizeIconObject( settings.icon );
+
if ( 'isPrivate' in settings ) {
deprecated( 'isPrivate', {
version: '3.1',
diff --git a/blocks/api/test/registration.js b/blocks/api/test/registration.js
index 29f437579a943d..5cc8421b616664 100644
--- a/blocks/api/test/registration.js
+++ b/blocks/api/test/registration.js
@@ -84,7 +84,9 @@ describe( 'blocks', () => {
expect( console ).not.toHaveErrored();
expect( block ).toEqual( {
name: 'my-plugin/fancy-block-4',
- icon: 'block-default',
+ icon: {
+ src: 'block-default',
+ },
save: noop,
category: 'common',
title: 'block title',
@@ -167,7 +169,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
- icon: 'block-default',
+ icon: {
+ src: 'block-default',
+ },
attributes: {
ok: {
type: 'boolean',
@@ -186,7 +190,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
- icon: 'block-default',
+ icon: {
+ src: 'block-default',
+ },
} );
} );
@@ -224,7 +230,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
- icon: 'block-default',
+ icon: {
+ src: 'block-default',
+ },
},
] );
const oldBlock = unregisterBlockType( 'core/test-block' );
@@ -234,7 +242,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
- icon: 'block-default',
+ icon: {
+ src: 'block-default',
+ },
} );
expect( getBlockTypes() ).toEqual( [] );
} );
@@ -276,7 +286,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
- icon: 'block-default',
+ icon: {
+ src: 'block-default',
+ },
} );
} );
@@ -289,7 +301,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
- icon: 'block-default',
+ icon: {
+ src: 'block-default',
+ },
} );
} );
} );
@@ -309,7 +323,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
- icon: 'block-default',
+ icon: {
+ src: 'block-default',
+ },
},
{
name: 'core/test-block-with-settings',
@@ -317,7 +333,9 @@ describe( 'blocks', () => {
save: noop,
category: 'common',
title: 'block title',
- icon: 'block-default',
+ icon: {
+ src: 'block-default',
+ },
},
] );
} );
diff --git a/blocks/api/utils.js b/blocks/api/utils.js
index 185f4961808e11..19c587b9a0acfc 100644
--- a/blocks/api/utils.js
+++ b/blocks/api/utils.js
@@ -1,12 +1,14 @@
/**
* External dependencies
*/
-import { every, keys, isEqual } from 'lodash';
+import { every, keys, isEqual, isFunction, isString } from 'lodash';
+import { default as tinycolor, mostReadable } from 'tinycolor2';
/**
* WordPress dependencies
*/
import { applyFilters } from '@wordpress/hooks';
+import { Component } from '@wordpress/element';
/**
* Internal dependencies
@@ -14,6 +16,14 @@ import { applyFilters } from '@wordpress/hooks';
import { getDefaultBlockName } from './registration';
import { createBlock } from './factory';
+/**
+ * Array of icon colors containing a color to be used if the icon color
+ * was not explicitly set but the icon background color was.
+ *
+ * @type {Object}
+ */
+const ICON_COLORS = [ '#191e23', '#f8f9f9' ];
+
/**
* Determines whether the block is a default block
* and its attributes are equal to the default attributes
@@ -40,3 +50,37 @@ export function isUnmodifiedDefaultBlock( block ) {
isEqual( newDefaultBlock.attributes[ key ], block.attributes[ key ] )
);
}
+
+/**
+ * Function that receives an icon as set by the blocks during the registration
+ * and returns a new icon object that is normalized so we can rely on just on possible icon structure
+ * in the codebase.
+ *
+ * @param {(Object|string|WPElement)} icon Slug of the Dashicon to be shown
+ * as the icon for the block in the
+ * inserter, or element or an object describing the icon.
+ *
+ * @return {Object} Object describing the icon.
+ */
+export function normalizeIconObject( icon ) {
+ if ( ! icon ) {
+ return { src: 'block-default' };
+ }
+ if ( isString( icon ) || isFunction( icon ) || icon instanceof Component ) {
+ return { src: icon };
+ }
+
+ if ( icon.background ) {
+ const tinyBgColor = tinycolor( icon.background );
+ if ( ! icon.foreground ) {
+ const foreground = mostReadable(
+ tinyBgColor,
+ ICON_COLORS,
+ { includeFallbackColors: true, level: 'AA', size: 'large' }
+ ).toHexString();
+ icon.foreground = foreground;
+ }
+ icon.shadowColor = tinyBgColor.setAlpha( 0.3 ).toRgbString();
+ }
+ return icon;
+}
diff --git a/docs/block-api.md b/docs/block-api.md
index 263266a2c9fcf2..3a688a09bd6d98 100644
--- a/docs/block-api.md
+++ b/docs/block-api.md
@@ -70,6 +70,21 @@ An icon property should be specified to make it easier to identify a block. Thes
icon: 'book-alt',
```
+An object can also be passed as icon, in this case, icon, as specified above, should be included in the src property.
+Besides src the object can contain background and foreground colors, this colors will appear with the icon
+when they are applicable e.g.: in the inserter.
+
+```js
+
+icon: {
+ // Specifying a background color to appear with the icon e.g.: in the inserter.
+ background: '#7e70af',
+ // Specifying a dashicon for the block
+ src: 'book-alt',
+} ,
+```
+
+
#### Keywords (optional)
Sometimes a block could have aliases that help users discover it while searching. For example, an `image` block could also want to be discovered by `photo`. You can do so by providing an array of terms (which can be translated). It is only allowed to add as much as three terms per block.
diff --git a/editor/components/autocompleters/block.js b/editor/components/autocompleters/block.js
index 08f2e015628f16..824e416445809a 100644
--- a/editor/components/autocompleters/block.js
+++ b/editor/components/autocompleters/block.js
@@ -42,7 +42,7 @@ export function createBlockCompleter( {
getOptionLabel( inserterItem ) {
const { icon, title } = inserterItem;
return [
-