Block Collections

Collections allow specific block types to be grouped together for added visibility in the editor’s Inserter menu, regardless of their categories. For instance, a plugin such as CoBlocks may register a Map block of category Widgets and a Post Carousel block of category Layout. By registering a collection for the coblocks namespace, these blocks will appear in the Inserter under their respective categories but also grouped under a CoBlocks collection.

The Block Collections API thus aims to improve the relationship of block discovery between users and plugin authors without compromising the semantics of block types, notably around categories (e.g. formatting, layout, widget, embed).

registerBlockCollection( 'coblocks', {
	title: 'CoBlocks',
	icon: brandAssets.categoryIcon,
} );

#5-4, #block-editor, #dev-notes

Introduce block variations API

Just as you can declare a block’s style variations when you register a block, a block type can define block variations the user can pick from. The difference is that, beyond changing the look, this field offers a way to apply initial custom attributes and inner blocks at the point of insertion.

By default, all the variations will show up in the Inserter in addition to the regular block-type item. But setting the isDefault flag for any of the listed variations will override the regular block type in the Inserter.

variations: [
    {
        name: 'wordpress',
        isDefault: true,
        title: __( 'WordPress' ),
        description: __( 'Code is poetry!' ),
        icon: WordPressIcon,
        attributes: { service: 'wordpress' },
    },
    {
        name: 'google',
        title: __( 'Google' ),
        icon: GoogleIcon,
        attributes: { service: 'google' },
    },
    {
        name: 'twitter',
        title: __( 'Twitter' ),
        icon: TwitterIcon,
        attributes: { service: 'twitter' },
    },
],

An object describing a variation defined for the block type can contain these fields:

  • name (type string) – The unique and machine-readable name.
  • title (type string) – A human-readable variation title.
  • description (optional, type string) – A detailed variation description.
  • icon (optional, type String | Object) – An icon helping to visualize the variation. It can have the same shape as the block type.
  • isDefault (optional, type boolean) – Indicates whether the current variation is the default one. Defaults to false.
  • attributes (optional, type Object) – Values that override block attributes.
  • innerBlocks (optional, type Array[]) – Initial configuration of nested blocks.
  • example (optional, type Object) – Example provides structured data for the block preview. You can set to undefined to disable the preview shown for the block type.
  • scope (optional, type String[]) – the list of scopes where the variation is applicable. When not provided, it assumes all available scopes. Available options: block, inserter.

#5-4, #block-editor, #dev-notes

New or Updated Blocks in WordPress 5.4

Social Icons Block

This new block lets users link to social media and other popular websites by using those sites’ logos. Initially called Social Links, Social Icons were an experimental feature in Gutenberg 6.5 but held out of WordPress 5.3. Since then, the Block Variations API has progressed to the point that Social Icons in Gutenberg 7.5 are much simpler and more stable – and ready for merge in WordPress 5.4.

This reimplementation is a breaking change in the way Social Icons are saved (see details). Only sites that have run the Gutenberg plugin since September are potentially concerned. 

In WordPress 5.4, the core block editor will not recognize any Social Icons blocks built before Gutenberg 7.5.

There are two ways to deal with this:

  • (Recommended method) Manually migrate any content with old Social Icons. Here’s how: load a post in the block editor (Gutenberg 7.5 or higher) and save it. The block editor will automatically update its contents. 
  • Keep the Gutenberg plugin installed after upgrading to WordPress 5.4. The plugin will give you manual backwards compatibility for the old Social Icons.

Buttons Block

This new block is a collection of buttons, because authors often need to use several at a time (for instance: download and read more buttons).

The buttons block shows each button as an individual button-block child of the Buttons block. You won’t be able to insert a button block outside Buttons, but your existing button blocks will work the way they always have.

In case you were using the button block as part of a template or a system that automatically inserted a button block, you’ll want to use the Buttons block with a nested button instead.

Plus, here’s some good news: you won’t need to migrate your existing button blocks. They’ll just work — again, as they always have.

#5-4, #block-editor, #dev-notes

Block Editor Keyboard Shortcuts in WordPress 5.4

In WordPress 5.4 a new package called @wordpress/keyboard-shortcuts has been introduced to centralize the registration/removal and documentation of the available keyboard shortcuts in the block editor screen.

Registering shortcuts

Registration of keyboard shortcuts should happen when first loading the screen/plugin. This can be achieved by calling the registerShortcut action.

wp.data.dispatch( 'core/keyboard-shortcuts' ).registerShortcut( {
	// Shortcut name (identifier)
	name: 'plugin/shortcut-name',

 	// Catergory (global, block or selection)
	category: 'block',

	// Description 
	description: 'Short description of your shortcut.',

	// The key combination used to trigger the shortcut
	// Could be just a single character or a character with
	// a modifier.
	keyCombination: {
		modifier: 'primaryAlt',
		character: 't',
	},

	// Some shortcuts can be triggered using several 
	// key combination, the aliases array is used to define
	// the alternative combinations
	aliases: [
		{
			modifier: 'primary',
			character: 'i',
		},
	],
} );

Registering a shortcut automatically add it to the keyboard shortcuts help modal to ensure its discoverability.

Implementing the keyboard shortcut behavior

The @wordpress/keyboard-shortcuts package also provides the useShortcut React hook to define the behavior triggered by the keyboard shortcuts.

import { useShortcut } from '@wordpress/keyboard-shortcuts';
import { useCallback } from '@wordpress/element';

const MyComponent = () => {
	useShortcut(
		// Shortcut name
		'plugin/shortcut-name',

		// Shortcut callback
		useCallback(
			( event ) => {
				// Do something when the keyboard 
				// shortcut is triggered
			},
			[]
		)
	);
}

Using this React hook instead of a custom implementation comes with a few advantages:

  • If the shortcut is unregistered by a third-party plugin, the callback is just ignored.
  • The shortcut key combination can be modified at runtime and the callback will still be called properly.

Removing existing shortcuts

Registered shortcuts can also be unregistered (and potentially replaced) by third-party plugins

wp.data.dispatch( 'core/keyboard-shortcuts' ).unregisterShortcut(
	'plugin/shortcut-name'
);

Next steps

In the next releases, this package will also allow offering a centralized UI to modify the keyboard shortcuts per user.

#5-4, #accessibility, #block-editor, #dev-notes

An updated Button component in WordPress 5.4

As the @wordpress/components package becomes the vehicle that implements more and more of the WordPress design system, and as that system matures, its components get more consistent and more coherent.

WordPress 5.4 adds a number of changes and enhancements to the Button component.

Button sizes

In keeping with the overall design direction of the project, the button default height is now 36px. So you no longer need to use the previous isLarge prop variation.

The Button still supports the isSmall variation.

import { Button } from '@wordpress/components';

const regularButton = <Button>My Button</Button>;
const smallButton = <Button isSmall>My Button</Button>;

To keep all the button variations consistent, their styles now declare specific heights.

If you’ve been relying on the previous buttons’ dynamic heights to make them adapt to their content, you’ll want to override the new fixed heights. Make sure you add the rule below:

height: auto;

Icon support

In previous versions, the components package offered a Button component for regular buttons and an IconButton for buttons with icons.

WordPress 5.4 merges those components. To show buttons with icons, pass an extra icon prop to the regular Button component. You can also mix text and icons.

import { Button } from '@wordpress/components';

const myIcon = (
  <svg xmlns="http://www.w3.org/2000/svg" viewport="0 0 20 20">
    <path r="M5 4l10 6-10 6V4z" />
  </svg>
);

const SimpleIconButton = <Button icon={ myIcon } label="Button label" />;

const IconAndTextButton = (
  <Button icon={ myIcon }>
    Button Text
  </Button>
);

Note: the IconButton is still available, but it’s officially deprecated.

Classname changes

In previous versions, icon buttons relied internally on the components-icon-button. With the merger of the Button and IconButton components, WordPress removes this class name and replaces it with .components-button.has-icon.

Recommendation: Don’t rely on any internal className a component might use. If you want to target a specific component, prefer adding your own className prop and use that instead.

Going further

Try out the Button component or the other wordpress/components. Check out the components Storybook.

#5-4, #block-editor, #components, #dev-notes

Controlling the Block Editor

One of the important use-cases of the block-editor is the possibility to control the experience programmatically. Controlling the experience means allowing agencies and developers to configure or restrict the usage of the different features available in the editor. This has always been one of the priorities while adding features to Gutenberg and it will continue to be.

As of today (included in WordPress 5.3), the block editor comes with a number of API to achieve this:

Blocks

Blocks are the main extensibility API of the editor. Thus, developers have the possibility to register custom blocks but they can also restrict the list of available blocks. This can be done both server-side and client-side. Here’s one of the available techniques to do so:

function my_plugin_allowed_block_types( $allowed_block_types, $post ) {
    if ( $post->post_type !== 'post' ) {
        return $allowed_block_types;
    }
    return array( 'core/paragraph' );
}
 
add_filter( 'allowed_block_types', 'my_plugin_allowed_block_types', 10, 2 );

Refer to the documentation for more block types restriction techniques.

Block Style Variations

Several core blocks offer style variations. For instance, the Buttons block comes with two variations “Fill” and “Outline”. The editor provides APIs to register custom style variations and to remove existing ones:

// Registers a style variation for quotes.
register_block_style(
    'core/quote',
    array(
        'name'         => 'blue-quote',
        'label'        => __( 'Blue Quote' ),
        'inline_style' => '.wp-block-quote.is-style-blue-quote { color: blue; }',
    )
);

// Unregisters the style variation named "fancy-quote"
unregister_block_style( 'core/quote', 'fancy-quote' );

Locked Templates

Locked Templates offer a way to restrict the content of Posts, Pages, CPTs to a given format.

For example the following template can be used to restrict the content of a Books CPT to an image, a paragraph for the author and a description.

array(
	array( 'core/image', array(
		'align' => 'left',
	) ),
	array( 'core/heading', array(
		'placeholder' => 'Add Author...',
	) ),
	array( 'core/paragraph', array(
		'placeholder' => 'Add Description...',
	) ),
)

Refer to the documentation more information about the Templates API.

Theme Supports

Colors

Several blocks offer color customization features. Developers can customize the default color palette to provide a consistent color scheme across the whole website.

add_theme_support( 'editor-color-palette', array(
    array(
        'name' => __( 'Strong magenta', 'themeLangDomain' ),
        'slug' => 'strong-magenta',
        'color' => '#a156b4',
    ),
    array(
        'name' => __( 'Light grayish magenta', 'themeLangDomain' ),
        'slug' => 'light-grayish-magenta',
        'color' => '#d0a5db',
    ),
) );

Users have the possibility to choose a custom color as well. This feature can also be disabled:

add_theme_support( 'disable-custom-colors' );

Additionally, it is possible to define an empty color palette to remove all color customization panels from the editor.

add_theme_support( 'editor-color-palette', array() );

Font sizes

Some blocks also allow the user to tweak the font size and developers have the possibility to define their own set of font sizes.

add_theme_support( 'editor-font-sizes', array(
    array(
        'name' => __( 'Small', 'themeLangDomain' ),
        'size' => 12,
        'slug' => 'small'
    ),
    array(
        'name' => __( 'Regular', 'themeLangDomain' ),
        'size' => 16,
        'slug' => 'regular'
    )
)

Developers can choose to remove the ability to use a custom font size.

add_theme_support( 'disable-custom-font-sizes' );

Additionally, it is possible to define an empty font sizes set to remove all font size customization panels from the editor.

add_theme_support( 'editor-font-sizes', array() );

Gradients

Recently, the ability to use gradients was added to the editor (only available in the Gutenberg plugin). Similarly to colors, developers can define a custom gradients palette, disable custom gradients, or disable the gradients entirely.

// Define a custom palette.
add_theme_support(
    '__experimental-editor-gradient-presets',
    array(
        array(
            'name'     => __( 'Vivid cyan blue to vivid purple', 'themeLangDomain' ),
            'gradient' => 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)',
            'slug'     => 'vivid-cyan-blue-to-vivid-purple'
        ),
        array(
            'name'     => __( 'Vivid green cyan to vivid cyan blue', 'themeLangDomain' ),
            'gradient' => 'linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)',
            'slug'     =>  'vivid-green-cyan-to-vivid-cyan-blue',
        ),
    )
);

// Disable custom gradients.
add_theme_support( '__experimental-disable-custom-gradients' );

// Disable gradients
add_theme_support( '__experimental-editor-gradient-presets', array() );

Third-party blocks

One of the strengths of the block editor is its block library. The community have built dozens of block plugins. But, while Core blocks do support the controlling APIs exposed above, a lot of third-party blocks don’t adapt properly to these options.

For more consistency between the different block libraries and the Core blocks, block authors are encouraged to support these options in their custom blocks.

The block primitives made available to block authors to build their blocks (e.g. the FontSizePicker component, the experimental useColors and useGradient hooks) do support these options by default, but if you’re using custom UIs, you’re encouraged to adapt to these options. You can access these settings by using the select( 'core/block-editor' ).getSettings()selector.

/*
 * @property {Array} colors Palette colors
 * @property {boolean} disableCustomColors Whether or not the custom colors are disabled
 * @property {Array} gradients Gradient palette
 * @property {boolean} disableCustomGradients Whether or not the custom gradients are disabled
 * @property {Array} fontSizes Available font sizes
 * @property {boolean} disableCustomFontSizes Whether or not the custom font sizes are disabled

 * @property {boolean} __experimentalEnablePageTemplates Whether the user has enabled the Page Templates
 */
const settings = wp.data.select( 'core/block-editor' ).getSettings();

Going further

As shown above, the block editor already provides a big number of APIs to control the experience, but it’s fair to acknowledge a lack of consistency and a few missing APIs (for instance to disable drop caps in Paragraph blocks).

In order to address this, I’m proposing a new block_editor_features hook that looks like this:

// Non-exhaustive representation of the block-editor configuration options.
$block_editor_features = array(
    'colors' => array(
        "enabled" => true,
        "colorPalette" => array(),
        "allowCustomColors" => true,
        "gradientPalette" => array(),
        "allowCustomGradients" => true,
    ),
    'typography' => array(
        "enabled" => true,
        "fontSizes" => array(),
        "allowCustomFontSizes" => true,
        "allowDropCap" => true,
    ),
);

apply_filters( 'block_editor_features', array $block_editor_features, WP_Post $post );

And these would be made available to block authors using a dedicated selector.

const features = wp.data.select( 'core/block-editor' ).getFeatures();

In addition to the PHP filter, this configuration can also be made available in the proposed theme.json file.

#block-editor, #core-editor, #gutenberg, #themes

Gutenberg Local Environment Rewrite

With the introduction of the WordPress Local Environment, the next step was to make this shiny new development environment available for Gutenberg to use.

tl;dr: If you currently run ./bin/setup-local-env.sh to use the Gutenberg environment, switch to running npm run env install, instead.

Gutenberg’s Local Environment was originally an experiment in making it easy to setup a local development environment. It was a success in some ways, in that it was easier than other methods, but it was by no means easy overall, particularly for non-developer contributors. Over time, it grew into a complex set of shell scripts, with surprising interdependencies and side effects. Being written in Bash made the scripts harder to maintain, and limited the potential audience to those who had a Bash-compatible terminal installed.

Basing Gutenberg’s Local Environment on Core’s has several immediate advantages:

  • It’s cross-platform, only requiring Docker to be installed and running. It also works with Docker Toolbox, allowing all Windows users (not just those with Windows 10 Pro) to use it.
  • The WordPress environment uses Docker images that we maintain for the purpose of Core development, which allows us to to customise them as we need.
  • It ensures that tests for Core and Gutenberg run in the same environment.
  • It’s a single environment, which helps avoid the naming clashes we’ve occasionally seen between the Core and Gutenberg environments.

More broadly for WordPress in the long term, tying the development processes of Core and Gutenberg a little closer together helps pave the way for the processes to merge over time.

Architectural Decisions

Removing the Docker config from Gutenberg entirely (barring a minimal template to hook into the Core environment), and requiring wordpress-develop to run on top of gives us significantly more future flexibility with the development environment.

Additionally, this continues to lay the groundwork for an environment that works as easily as possible for non-developer contributors. In particular, this environment is intended to be usable by all feature plugins, allowing them to easily enable new contributions in a familiar environment. (Or, an environment that will become familiar, at least. 😉)

There are several reasons for putting the main Docker config in Core, and extending it in Gutenberg:

  • Having a single docker-compose.yml file in Core, as opposed to having one in Core, one in Gutenberg, and many more in other feature plugins that will be able to use this, means that there’s only set of Docker containers running. Docker has been observed getting a little weird when trying to run containers with the same names, which mount volumes with the same names.
  • Having multiple docker-compose.yml files doesn’t lend itself to testing plugins together: each plugin ends up with its own environment which can’t talk to others.
  • Managing how plugins can mount older versions of WordPress becomes exponentially more difficult if the docker-compose.yml config needs to work for every WordPress branch (this is a requirement, so that auto updates can be worked on in old branches), rather than being able to tweak the config for each branch.
  • Ideally, feature plugins shouldn’t need to do much customisation of the Docker environment, beyond mounting their particular volumes. Exposing the entire docker-compose.yml file is overkill.

That said, not every Gutenberg contributor will have a WordPress source clone, or want to manage one. So, there’s also a helper which will download and install a copy of the WordPress source repo, when needed.

Much like the WordPress environment, however, the Gutenberg environment doesn’t automate everything. As we found with the original Gutenberg environment, too much automation tended to make it difficult (particularly for more advanced contributors) to customise or debug their environment. For example, the scripts no longer install NVM, switch your Node version, or run npm install.

Using the Gutenberg Local Environment

If you don’t have a WordPress repo to work with, just run npm run env install, that’ll take care of downloading WordPress, building it, and connecting Gutenberg into it.

If you prefer to hook into your own WordPress source checkout (particularly useful for fixing issues that span Core and Gutenberg), set the WP_DEVELOP_DIR environment variable to your WordPress source directory, and run npm run env connect. This will write a docker-compose.override.yml file to the WordPress source directory, and restart the Docker containers. The Docker override file contains the appropriate volume settings for mounting Gutenberg within the WordPress environment.

Running npm run env will give you a complete list of the available commands, and what they’re for.

The WordPress Local Environment configuration options are also available for configuring the Gutenberg environment.

Adding the Local Environment to another feature plugin

The @wordpress/scripts documentation includes information about including and configuring the local environment.

There currently isn’t a clean method for sharing a managed WordPress checkout (ie, one installed by npm run env install) between plugins. Let’s figure out how that could work!

Also, this process is still mostly untested, so please ping me earlier rather than later if you run into problems. 🙂

Developing Plugins/Themes with the Local Environment

At this stage, the Local Environment is intended for developing Core, and Feature Plugins: it doesn’t have the config options that the wider plugin/theme ecosystem needs, and the base Docker images are built for Core purposes.

Of course, I know y’all will try to use it, anyway. 😉 Testing and feedback is welcome, but please be aware that fixes and features will prioritise Core/Gutenberg requirements for the foreseeable future. Patches and PRs which recognise and allow for this are the best way for additional changes to land. 💖

What’s Next?

Please try out the new Gutenberg Local Environment, and report any issues you run into over on the Gutenberg repository.

If you have thoughts about making this aspect of contributing to Core/Gutenberg easier, I’d love to hear about them!

#block-editor, #gutenberg

Block Editor Detection Improvements in 5.2

In 5.0, WP_Screen::is_block_editor() was introduced to allow developers to conditionally execute code depending on whether the block editor is being loaded. This method returns the is_block_editor property of the WP_Screen instance. However, there were some large gaps in the loading process where an incorrect value could potentially be returned.

For example, when using the current_screen action hook, the value was always false, even when the user was visiting a block editor enabled screen. This happened because block editor support is flagged much later in the loading process when edit-form-blocks.php is included.

function myplugin_current_screen( $screen ) {
	if ( $screen->is_block_editor ) {
		// This conditional would never execute.
	}
}
add_action( 'current_screen', 'myplugin_current_screen' );

This has been fixed in 5.2 to account for all possible scenarios when a post is edited. However, there are still a few very narrow edge cases when a new post is created where WP_Screen::is_block_editor() may still incorrectly indicate block editor support.

Edge Cases When Creating New Posts

The use_block_editor_for_post() function and replace_editor filter require a WP_Post object to be passed as a parameter. Because a new post has not yet been created when WP_Screen is instantiated, it can only make its best guess whether the page is loading the block editor.

When creating a new post, WP_Screen will set is_block_editor property to the value returned by use_block_editor_for_post_type() for the current post type. In most cases, this guess will be correct. But, the following scenarios have edge cases to consider.

  • When the replace_editor filter is used to replace the editor, this value may be incorrect.
  • When the use_block_editor_for_post filter is used to change block editor support.

For both of these scenarios, the use_block_editor_for_post_type filter can be used to ensure the correct value in most circumstances.

function myplugin_replace_editor_filter( $replace_editor, $post ) {
	// Logic to replace editor.
}
add_filter( 'replace_editor', 'myplugin_replace_editor_filter', 10, 2 );

function myplugin_replace_editor_post_type( $use_block_editor, $post_type ) {
	// Similar logic to replace editor, but without a WP_Post object to work with.
}
add_filter( 'use_block_editor_for_post_type', 'myplugin_replace_editor_post_type', 10, 2 );

With this filter, all scenarios that do not require checking a specific property of a post (a taxonomy term, meta value, etc.) can be accounted for. For example, filtering based on user capability, site option, or user meta value for editor preference are all possible using use_block_editor_for_post_type.

When WordPress creates a new post, it uses the get_default_post_to_edit() function. This function creates a new post in the database using wp_insert_post() and then allows the default content, title, and excerpt to be filtered. When terms, post meta, or content are added to posts with actions such as save_post or wp_insert_post, it is possible that this could change the block editor support for the post being created.

This scenario would result in WP_Screen:is_block_editor possessing an incorrect value from the current_screen action until roughly the load-{$pagenow} action.

Logic should be added to the use_block_editor_for_post_type filter to account for these scenarios (which are normally post type specific) and guarantee the accuracy of WP_Screen::is_block_editor().

For more information on this, consult the ticket on Trac (#46195), or the changeset ([45224]).

#5-2, #block-editor, #dev-notes, #gutenberg