Changes to the screen-reader-text CSS class in WordPress 4.9

The screen-reader-text CSS class is a small bit of CSS used in WordPress to visually hide text and make it still available to assistive technologies, screen readers, and any other software reading a page.

For a number of years, this CSS class has used an incorrect syntax for the clip property to deal with old Internet Explorer versions. WordPress 4.8 officially dropped support for Internet Explorer versions 8, 9, and 10. This is a good opportunity to update screen-reader-text to a modern, correct syntax and standardize it across the codebase. Furthermore, the clip property is deprecated: some browsers may still support it, but it is in the process of being dropped and it may cease to work at any time.

Worth noting this change applies only to the CSS class used in the WordPress admin pages.

Here’s how the old CSS class looks like:

.screen-reader-text {
	position: absolute;
	margin: -1px;
	padding: 0;
	height: 1px;
	width: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	border: 0;
	word-wrap: normal !important;

And here’s how the new CSS class looks like in WordPress 4.9:

.screen-reader-text {
	border: 0;
	clip: rect(1px, 1px, 1px, 1px);
	-webkit-clip-path: inset(50%);
	clip-path: inset(50%);
	height: 1px;
	margin: -1px;
	overflow: hidden;
	padding: 0;
	position: absolute;
	width: 1px;
	word-wrap: normal !important;

The only changes are the clip property value new syntax and the introduction of clip-path.

In the vast majority of cases this small change shouldn’t require any update to plugins and themes. There’s only one case to be aware of and that’s when the screen-reader-text CSS class is used to dynamically reveal some text. In a very few cases, WordPress itself reveals some visually hidden text. For example, when there’s no JavaScript support or on small screens, screen-reader-text gets reset to make the visually hidden text visible again:

.no-js .some-element .screen-reader-text {
	position: static;
	-webkit-clip-path: none;
	clip-path: none;
	width: auto;
	height: auto;
	margin: 0;

If you’re using a similar CSS technique in your plugin or theme admin pages, don’t forget to reset the new clip-path property too.

For more details, see the related changeset and Trac ticket.

#4-9, #dev-notes

Code Editing Improvements in WordPress 4.9

The themes outlined for WordPress 4.9 are “editing code, managing plugins and themes, a user-centric way to customize a site, and polishing some recently added features over this last year.” Within the themes of editing code and polishing recent features, we’re improving the code editing functionality in the Customizer’s Additional CSS feature, the Custom HTML widget, and the Plugin and Theme file editors. We included these improvements to code editing among the 4.9 goals and this release is packed with them.

CodeMirror: Syntax Highlighting, Linting, and Auto-completion

The most visible and drastic improvement to code editing in 4.9 is that there is now an actual code editing control rather than just a textarea input. If you’ve been using WordPress for a long time (over 8 years), this may sound like déjà vu. Syntax highlighting for the theme and plugin editors was originally introduced in WordPress 2.8 (#9173) but it was removed shortly after in 2.8.1 due to browser compatibility problems with the “CodePress” library (no relation to WordPress). So in the 8 years since the feature was re-proposed in #12423, after considering a slew of code editor libraries, we decided on incorporating CodeMirror:

CodeMirror is a versatile text editor implemented in JavaScript for the browser. It is specialized for editing code, and comes with a number of language modes and add-ons that implement more advanced editing functionality. ¶ A rich programming API and a CSS theming system are available for customizing CodeMirror to fit your application, and extending it with new functionality.

You have probably already used this CodeMirror library a lot online, since it powers the editors in many familiar products and services including, Bitbucket, Chrome’s DevTools, Codepen, Firefox Developer Tools, GitHub, and JSFiddle, among many others. In the WordPress world specifically CodeMirror is also very familiar. Jetpack switched from ACE to CodeMirror in 2013 for its Custom CSS module, and there are close to 100 search results for CodeMirror on the plugin directory. Many of them should be updated to re-use CodeMirror as bundled with core as well. See below for some details on how to do that.

The syntax highlighting abilities of CodeMirror can help authors catch many mistakes visually while writing code, as the color coding can quickly clue in that something isn’t right. In addition to color coding, WordPress also enables by default the add-ons which will auto-close brackets and tags, and then also highlight matching braces and tags which have already been written.

CodeMirror also supports linting to actually add explicit error checking beyond just stylistic helps. WordPress is initially bundling the following linters: CSSLint, JSHintHTMLHint, and JSONLint. See #41873 for adding a PHP linter as well, though as described below, the theme and plugin editors have a more robust means of checking for PHP errors by running the code on the server itself. The linters will report either errors or warnings with your code:

When a linter finds an error in your code (CSS, HTML, JS, or JSON) the code editor in WordPress will prompt you to fix the error before allowing you to proceed with saving. The nature of this error notice varies by whether the code editor is in Custom CSS control, Custom HTML widget, or the file editor.

Another feature of CodeMirror which reduces mistakes is auto-completion (or hinting). As you start typing out a CSS property, JavaScript DOM object, or HTML tag, an autocomplete dropdown will appear which you can use your keyboard to select an option:


There is still room for improvement with auto-completion (see #42213), but the feature does help suggest possibilities when you generally have an idea of what you’re wanting to enter.

Theme and Language Modes

For the CodeMirror library now bundled in core, we decided to not include any of the alternate themes, so the default theme is used with some styles added to bring it in line with core. Additionally we also did not include all of the language modes, as many would be very unlikely to be relevant in the WordPress context (e.g. Fortran). The WordPress-relevant modes we are including with the core bundle are: clikecss, diff, htmlmixed, http, javascript, jsx, markdown, gfm, nginx, php, sass, shellsql, xml, and yaml. If a plugin wants to use a mode that is not bundled with core, they may bundle and enqueue the mode script separately (e.g. fortran.js); a plugin may also bundle and enqueue a custom theme if desired.

Accessibility and User Preference

One of the biggest challenges when exploring the incorporation of a code editor library into WordPress was the concerns raised regarding accessibility. For users of screen readers, a plain textarea is just going to be easier to navigate and use. CodeMirror does have an inputStyle option which:

Selects the way CodeMirror handles input and focus. The core library defines the "textarea" and "contenteditable" input models. On mobile browsers, the default is "contenteditable". On desktop browsers, the default is "textarea". Support for IME and screen readers is better in the "contenteditable" model. The intention is to make it the default on modern desktop browsers in the future.

The code editor in WordPress goes ahead and explicitly defines contenteditable as being the default for both desktop and mobile due to better accessibility. Nevertheless, since there are still accessibility concerns we decided to not yet integrate CodeMirror in the post editor’s Text tab; as CodeMirror is enabled by default it could impede users of screen readers from performing the primary writing workflow upon upgrading to 4.9. Additionally, it doesn’t make sense to work on integrating CodeMirror in the post editor since it is being heavily revamped right now in Gutenberg; we should instead focus on integrating CodeMirror into Gutenberg itself.

Lastly, if a user still does not want the CodeMirror library to be used when they edit code then there is now a user preference to turn it off. It is available on one’s user profile and it is called “Syntax Highlighting”. Again, it is enabled by default:

Additional CSS Integration

When the Additional CSS feature was introduced in 4.7, it used a plain textarea to edit the CSS code in the Customizer. For several years prior, Jetpack had already featured a Custom CSS module but it allowed CSS to be edited via a CodeMirror editor on an Edit CSS admin screen. After 4.7 was released, Jetpack was updated to use Additional CSS in the Customizer instead, but enhanced it with the CodeMirror editor it had used on its Edit CSS admin screen. So now in WordPress 4.9, core is following suit and integrating CodeMirror into the Additional CSS feature as well (#38707), and there’s now an issue for Jetpack to newly re-use CodeMirror as bundled in core.

One key improvement from the initial implementation of Additional CSS is in regards to the detection of syntax errors. In 4.7 the error detection logic merely checked to make sure that the number of braces, brackets, and parentheses were balanced. This was not ideal because there were false positives when a balancing character was present in a comment (e.g. #39198). The goal was to eventually harden validation of CSS syntax validity by utilizing a tokenizer/parser (#39218). Instead of having to implement this logic in PHP, however, we now rely on client-side logic via CodeMirror and CSSLint to check for CSS errors and the unreliable server-side validation has been removed.

Code Editor Customizer Control

As when the Additional CSS feature was first introduced as being extensible, the updates feature new extensibility as well. When the feature was under development in 4.7 we debated whether or not to add a reusable code editor control for the Customizer. At that time we decided to opt for a regular textarea control with some enhancements since there wasn’t enough unique about the code editor to justify a separate control at that time. With the availability of CodeMirror, however, there is now justification for a reusable code editor Customizer control (#41897). This control is what is used to power the Additional CSS editor.

The code editor control may be registered in PHP via instantiating the WP_Customize_Code_Editor_Control class as can be seen in core. It allows you to pass a code_type param to indicate the file type being edited. Alternatively, an editor_settings array param may be passed which is the same format the new wp_enqueue_code_editor() function accepts (described below).

As with any Customizer control, the code editor control may also be added dynamically with just JavaScript. One example of this can be seen in the Customize Posts CSS plugin. Another example would be to add a second code editor control for Additional CSS to show up in the Colors section of the Customizer:

wp.customize.control.add( new wp.customize.CodeEditorControl( 'custom_colors', {
	section: 'colors',
	priority: 100,
	label: 'Custom CSS',
	editor_settings: {
		codemirror: {
			mode: 'css'
	setting: 'custom_css[' + wp.customize.settings.theme.stylesheet + ']'
} ) );

The code editor control registered for Additional CSS can itself also be extended. Either the registered custom_css control can be swapped out for a subclass of wp.customize.CodeEditorControl in JS (as seen in Jetpack PR), or the existing control can be modified at runtime. For example, in keeping with 4.7’s Custom SCSS Demo plugin, here is how you can dynamically change the Additional CSS control to use SCSS instead of plain CSS:

wp.customize.control( 'custom_css', function( control ) {

	 * CodeMirror gets initialized once the control's containing
	 * section is expanded. Note that if the Syntax Highlighting
	 * user preference is disabled, then the deferred will be
	 * rejected.
	control.deferred.codemirror.done( function() {
		var scssOptions = {
			mode: 'text/x-scss',
			lint: false, // CSSLint doesn't like SCSS.
			gutters: [] // No need for 'CodeMirror-lint-markers'.
		_.each( scssOptions, function( value, option ) {
			control.editor.codemirror.setOption( option, value );
		} );
	} );
} );

And similarly, here is how you can change the default from CSS to SCSS via PHP:

add_action( 'customize_register', function( $wp_customize ) {
	$control = $wp_customize->get_control( 'custom_css' );
	if ( $control instanceof WP_Customize_Code_Editor_Control ) {
		$options = array();
		if ( isset( $control->editor_settings['codemirror'] ) ) {
			$options = isset( $control->editor_settings['codemirror'] );
		$control->editor_settings['codemirror'] = array_merge(
				'mode' => 'text/x-scss',
				'lint' => false,
				'gutters' => array(),
}, 11 );

Custom HTML Widget Improvements

In WordPress 4.8.1 a dedicated Custom HTML widget was introduced in order to take over the role the Text widget had for adding arbitrary markup to sidebars, as the Text widget in 4.8 featured the TinyMCE visual editor. This new Custom HTML widget was introduced as essentially a clone of the old Text widget, aside from the absence of the “automatically add paragraphs” checkbox. Well now in WordPress 4.9 the Custom HTML widget comes into its own as it also now incorporates CodeMirror to provide users with syntax highlighting, auto-completion, and error checking. As with the Additional CSS feature, if you make a coding error in the Custom HTML widget, you will be blocked from saving until you fix the error. This guards against a misplaced div tag from breaking your site’s entire layout.

On multisite installs or any site on which an admin user lacks the unfiltered_html capability, there are restrictions for what HTML a user can provide in post content, Text widgets, and Custom HTML widgets alike. In 4.8.1 we resorted to listing out some common tags that would be illegal when a user cannot do unfiltered_html. With CodeMirror, however, this is greatly improved due to its integration with HTMLHint and because it is extensible to allow custom rules to be added. There is now a custom kses rule for HTMLHint (htmlhint-kses.js) which checks HTML for any tags or attributes that are not returned by wp_kses_allowed_html( 'post' ). This means that we don’t need to tell users what they can’t do if they have no intention of doing it in the first place, and HTMLHint provides contextual inline error reporting when they do provide something invalid. Plus, since saving is blocked when there are errors, a user’s illegal HTML will not be silently stripped from them when they attempt to save (as wp_kses_post() is still applied on the content when saving on the server).

The CodeMirror component in the Custom HTML widget is integrated in a similar way to TinyMCE being integrated into the Text widget, adopting the same approach for integrating dynamic JavaScript-initialized fields. See custom-html-widgets.js which exports a wp.customHtmlWidgets object to JS.

Just as CodeMirror has been integrated into the Custom HTML widget, once 4.9 is released a logical next step would then be to integrate CodeMirror into Gutenberg’s Custom HTML block, as per PR comment. Similarly, once CodeMirror is available in core it can then be explored for use in Gutenberg’s Text view (see issue).

Theme and Plugin File Editors

Now, about those theme and plugin editors. The file editor in WordPress has been the subject of much debate and skepticism over the years. This may be also why hasn’t received a lot of love in terms of improvements. For reasons why the file editor is still a valuable part of WordPress in its mission to democratize publishing, please see @melchoyce‘s post “From No Code to Pro Code”. She goes on to outline a few ways that the file editor can be improved and in WordPress 4.9 almost all of them have been implemented and beyond.

Nevertheless, when a user first visits the theme or plugin editor, they will be presented with the new warnings as follows:

Notice how the theme editor has a link directing a user to the Additional CSS feature in the Customizer. It is the hope that CodeMirror will be primarily used in Additional CSS and the Custom HTML widget, but for users who do need to make theme and plugin changes the editors have been vastly improved.

The file editors now also feature the same CodeMirror-powered syntax highlighting, auto-completion, and error checking. The allowed file extensions in the file editors can edit have been expanded to include formats which CodeMirror has modes for: conf, css, diff, patch, html, htm, http, js, json, jsx, less, md, php, phtml, php3, php4, php5, php7, phps, scss, sass, sh, bash, sql, svg, xml, yml, yaml, txt. In addition to increasing the number of editable file types, the file editors also now allow you to edit files deeper than two directories deep. And now given that the file list can be much longer than before, the files and their directories are now presented in an scrollable expandable tree like most editors provide:

When editing CSS, JS, HTML, and JSON files there is the same error checking powered by client side linters. As with Additional CSS and the Custom HTML widget, if a linter detects an error it will display an error and block you from saving the change. Here there is also a way for a user to override the error to proceed with saving anyway:

When editing PHP files, however, client-side linting is not enough (though it would be a nice enhancement, see #41873). If attempting to call an undefined function this will not be a syntax error, but it will cause a fatal error and whitescreen your site. The plugin editor did previously have some basic safeguards for this by temporarily deactivating the plugin and then re-activating it in a sandbox to check for fatal errors, though it was not very reliable (see #39766). And even when it was able to check for errors, a fatal error would result in the plugin being deactivated, a plugin which could be critical to a site to function properly. For themes on the other hand, there was no such ability to temporarily deactivate the theme and do a sandboxed check for fatal errors since a theme cannot be deactivated like a plugin can.

Ultimately what was worked out in #21622 was a new sandboxed method for making PHP file changes in both plugins and themes. When attempting to save a PHP file edit for a plugin or theme, during the user’s save request WordPress will write the file to disk after first copying the old file’s contents into a variable. Then immediately after writing the change it will do a loopback request back to the file editor screen with the user’s same cookies to check to see if the PHP file edit would lock them out of the editor. If that loopback request generates a PHP fatal error, then the original PHP file is restored. Otherwise, if there is no fatal error then WordPress will open another loopback request to the homepage of the site to check if there is a fatal error generated there. If so, again, the PHP file edit is undone with the old version of the file restored. At that point, an error message is shown to the user informing them of what specifically the error was and prompting them to fix it. The user’s modifications to the PHP file remain in the editor for them to fix (also these save requests now happen over Ajax so the user never leaves the page). If they try leaving the page without fixing the error and successfully re-saving, they’ll get an “Are you sure?” dialog informing them they would lose their changes, in the same way as leaving the Customizer or the Add New Post screen. If the loopback requests aren’t able to complete, the file edits will also be reverted and the user will be prompted to use SFTP to edit the file.

The JavaScript powering the new updated interface for the theme and plugin editors is located in theme-plugin-editor.js, which exports a wp.themePluginEditor object.

Code Editor APIs

The Customizer code editor control, Custom HTML widget, and file editor all make use of an underlying “code editor” API that provides an abstraction on top of CodeMirror. In PHP there is the wp_enqueue_code_editor() function which is named and functionally similar to wp_enqueue_editor() for TinyMCE. The wp_enqueue_code_editor() function takes an array of args, including the ability to specify the file type that you intend to edit, or else the file name itself. Alternatively, you can pass a codemirror array arg that has the same structure as what you would pass when initializing CodeMirror in JS. Then depending on the language mode that is either explicitly provided via codemirror arg or which is deduced from the file or type args, the function will specify various defaults depending on the selected mode. For example, if editing CSS then it will enable linting and if editing HTML it will enable the auto-closing of tags. Once the settings array is fully assembled it is then passed into a wp_code_editor_settings filter to give plugins a chance to further modify the settings. If this filter returns false or if the user had previously disabled the syntax highlighting preference, then the function will return false and no scripts will be enqueued. Otherwise, the function will proceed to then enqueue the code-editor script and style along with the wp-codemirror script/style dependencies and then any supporting linter scripts.

The wp_enqueue_code_editor() function will exported its settings array to wp.codeEditor.defaultSettings in JS while also returning it to that a feature can directly pass it into the wp.codeEditor.initialize() API. This initialize method is modeled after CodeMirror.fromTextArea() in that it takes a textarea object or ID as its first argument and then the settings as its second. In addition to the settings exported from wp_qneueue_code_editor() the settings passed into the initialize method can also include several callbacks including onChangeLintingErrors, onUpdateErrorNotice, onTabPrevious, onTabNext. These callbacks are what the various integrations rely on to manage the displaying of linting errors as well as ensuring keyboard navigation.

Here is a simple example of turning the user’s bio into a CodeMirror HTML editor on their profile screen:

add_action( 'admin_enqueue_scripts', function() {
	if ( 'profile' !== get_current_screen()->id ) {

	// Enqueue code editor and settings for manipulating HTML.
	$settings = wp_enqueue_code_editor( array( 'type' => 'text/html' ) );

	// Bail if user disabled CodeMirror.
	if ( false === $settings ) {

			'jQuery( function() { wp.codeEditor.initialize( "description", %s ); } );',
			wp_json_encode( $settings )
} );

As noted above, CodeMirror and its bundled modes and add-ons are registered in a wp-codemirror script handle. Also important to note here that this script does not define a global CodeMirror object but rather a wp.CodeMirror one. This ensures that other plugins that may be including other CodeMirror bundles won’t have conflicts. This also means that if you do want to include fortran.js from CodeMirror, that you’ll need to bundle it to call wp.CodeMirror.defineMode() instead of CodeMirror.defineMode(). A workaround for having to do this would be the following, but be aware of potential conflicts:

	'window.CodeMirrror = wp.CodeMirror;'

Development History

The integration of CodeMirror into core was initially worked on in the Better Code Editing feature plugin on GitHub. A full development history can be found there in the issues, pull requests, and commit log.

The principal contributors to code editing in this release were @afercia, @helen, @georgestephanis, @obenland, @melchoyce, @westonruter, and @WraithKenny.

The key tickets related to code editor improvements in 4.9 are:

  • #6531: Recursively search for files in theme and plugin editors
  • #12423: Include default code editor
  • #21622: Validate or sandbox theme file edits before saving them (as is done for plugins)
  • #24048: Code Editors: Increase the usability of Code Editor’s files list
  • #31779: Warn users before using a built-in file editor for the first time
  • #38707: Customizer: Additional CSS highlight, revisions, selection, per-page, pop-out
  • #39218: Customize: Harden validation of CSS syntax validity by utilizing tokenizer
  • #39766: Plugin does not gracefully fail when editing active plugin causes fatal error
  • #39892: Default value in Additional CSS
  • #41073: Linting code changes: prevent saving, or add confirm message
  • #41870: Code Editor: Add grunt task for building new CodeMirror bundles from external dependencies
  • #41872: Code Editor: Minor accessibility improvements to the CodeMirror editing areas
  • #41887: Code Editor: Error disables the Update File button.
  • #41897: Code Editor: Add reusable code editor Customizer control

#4-9, #codemirror, #dev-notes

Multisite Focused Changes in 4.9

Here’s an overview of the developer facing changes made in multisite for the 4.9 cycle. If you’re interested in more detail, checkout the full list of tickets.

clean_blog_cache() replaces refresh_blog_details()

Since 3.5, refresh_blog_details(), which accepts a site ID, has been a wrapper of the clean_blog_cache() function, which requires a site object.

In WordPress 4.9, clean_blog_cache() has been adjusted to also accept a site ID and to invalidate caches for a deleted site in the same way. From now on clean_blog_cache() should be used instead of refresh_blog_details() which will be deprecated in a future release.

More importantly, the refresh_blog_details action has been deprecated in favor of the clean_site_cache action. See #40201.

New function get_main_site_id()

The WP_Network class has historically contained a $blog_id property indicating the ID of the main site of that network. However, since this property was never part of the wp_site database table, it is set manually in the multisite bootstrapping process. This results in it only being set for the current network. For any other network, code like get_network( $id )->blog_id would return 0.

The new get_main_site_id() function introduced in 4.9 provides the site ID of any network in an easy way. The function accepts an optional $network_id parameter, which defaults to the current network. Furthermore the magic property logic in WP_Network has been adjusted so that the $blog_id property (and its magic $site_id equivalent) is automatically set when requested. This ensures get_network( $id )->blog_id will always return a meaningful value. See #29684.

Refactored user capability and role switching

Switching the available roles and the current user’s capabilities no longer happens in switch_to_blog() and restore_current_blog(). Instead it has been moved to a new function, wp_switch_roles_and_user(), which is hooked into the site switching process. This provides a performance improvement by temporarily unhooking the function in cases where roles and capabilities do not need to be switched.

In addition, the available user roles are now correctly switched when switching sites, with refactored behavior in the WP_User and WP_Roles classes making this possible. These changes are more closely explained in the 4.9 post about role and capability improvements. For related tickets, see #36961 and #38645.

Site administrators can edit user roles through the REST API

While site administrators cannot edit user details in multisite, they are able to modify a user’s roles. In WordPress 4.9 this can now be achieved through the REST API by making a request such as PUT wp/v2/users/<id> and passing only the roles argument in the request body. No other arguments must be given as those would require the current user to have network administrator capabilities. See #40263.

Other Notes

  • The new can_add_user_to_blog filter can be used to prevent a user from adding specific users to a site or with a specific role. See #41101.
  • The old site network admin email address gets notified of a change to the address. See related security improvements for 4.9 and #39117.

#4-9, #dev-notes, #multisite, #networks-sites

Improvements for roles and capabilities in 4.9

Here is an overview of the developer facing changes focused on user roles and capabilities for the 4.9 cycle. If you’re interested in more detail, checkout the full list of tickets.

New Capabilities

Activating and deactivating plugins

It is now possible to manage capabilities for activating and deactivating plugins more granularly through the following new capabilities:

  • activate_plugin checks whether a user can activate a specific plugin. When checking the capability, it gets passed the plugin file (such as current_user_can( 'activate_plugin', 'my-plugin/my-plugin.php' )).
  • deactivate_plugin works similar to activate_plugin, but checks whether a user can deactivate a specific plugin as the name indicates.
  • deactivate_plugins allows to check whether a user can generally deactivate plugins.

By default, all of the above capabilities map to the existing primitive capability activate_plugins, so there is no change in behavior by default. However they make it possible to customize the behavior, for example to prevent specific users from activating or deactivating specific plugins. See #38652 for background discussion.

Installing and updating language files

The other group of new meta capabilities deals with installing and updating language files / translations:

  • install_languages checks whether a user can install new language files.
  • update_languages checks whether a user can apply language file updates.

By default, the capabilities are granted to a user when they have at least one of the existing update_core, install_plugins or install_themes capabilities. In addition, if wp_can_install_language_pack() returns false, the capability checks will return false as well. Again there is no change in behavior, but these capabilities allow customizing permissions more granularly, for example to not allow any updates other than language file updates. See #39677 for background discussion.

Hardening security against prohibited actions

When going through the map_meta_cap() function, several capabilities end up mapping to a value of do_not_allow, which is not an actual capability that should be used, but rather indicate that a user should under no circumstances be allowed to perform the respective action. However, it has historically been possible to manually grant users do_not_allow as an actual capability, which is a bad practice and would cause unexpected behavior. As of 4.9, it is no longer possible to do that. See #41059 for background discussion.

Refactored user capability and role switching in multisite

In multisite, switching the available roles and the current user’s capabilities no longer happens in switch_to_blog() and restore_current_blog(), instead it has been moved to a new function wp_switch_roles_and_user() which is hooked into the site switching process. This allows to improve performance by temporarily unhooking the function in cases where roles and capabilities do not need to be switched.

Furthermore the logic for both switching user capabilities in WP_User and switching available roles in WP_Roles has been refactored to work in a similar manner and provide more granular methods:

  • The WP_User::for_blog() and WP_User::_init_caps() methods have been deprecated in favor of WP_User::for_site().
  • WP_Roles::_init() has been deprecated in favor of WP_Roles::for_site().
  • Both WP_User and WP_Roles now provide a get_site_id() method to retrieve the ID for which the user’s capabilities/available roles respectively are currently initialized.

All these changes heavily benefit the process of switching sites, particularly by fixing a bug where available roles were not switched correctly prior. See #36961 and #38645 for background discussion.

Having a clean foundation now, several areas now deal with the available roles correctly when in a switched state. See #42013, #42014 and #42015 for the individual tickets.


#4-9, #dev-notes

Account Security Improvements in WordPress 4.9

A few account security enhancements have gone into WordPress 4.9. The intention is to make it more difficult for an attacker to take over a user account or a site by changing the email address associated with the user or the site, and also to reduce the chance of a mistaken or erroneous change causing you to get locked out.

  • In order to change your user account email address, the site admin email address, or the network admin email address on Multisite, a link now needs to be clicked in a confirmation email that gets sent to the new email address. This behaviour has existed for years on sites within a Multisite network — the functionality has now been ported to single site installations too. See #16470, #39118, and #39119.
  • The old site admin email address now gets notified of a change to the address (this includes the network admin email address on Multisite too). See #39117.
  • The email that’s sent to a user’s old email address when their email address is changed now includes the new email address. See #39112.

#4-9, #dev-notes

Introducing the Gallery widget

In the last major release we introduced Media Widgets for Images, Video, and Audio. Per that dev note:

WordPress 4.8 includes media widgets (#32417) for not only images (#39993) but also video (#39994) and audio (#39995), on top of an extensible base for introducing additional media widgets in the future, such as for galleries and playlists.

Now in the upcoming 4.9 release this Gallery widget (#41914) has just landed in trunk in [41590]. Just as users can add galleries to their post content they too can add galleries to their sidebars. The media widgets are being developed with Gutenberg in mind, as widgets are essentially proto-blocks. Gutenberg has ported the Categories and Recent Posts widgets as dynamic blocks so that users can add to their posts what was formerly restricted to sidebars. In the same way, the media widgets are allowing for content that was formerly restricted to post content to also be available for addition to widget areas. As Gutenberg matures, widgets are planned to eventually transition over to use blocks, and the widgets for images, video, audio, and galleries will be able to be migrated over at that time. In the mean time, the user should not have to know there is any difference between post content and widget areas. Once the migration from widgets to blocks is complete, users shouldn’t actually perceive any fundamental change in this regard.

Here are four screenshots that show how the Gallery widget is created and updated:


You’ll note that the widget re-uses the same media modals that a user is familiar with when adding and editing galleries in the post editor.

Code Reference

PHP: wp-includes/widgets/class-wp-widget-media-gallery.php
JS: wp-admin/js/widgets/media-gallery-widget.js

Field Type Default Description
title string "" Title for the widget
ids array [] Attachment IDs
columns integer 3 Columns
size string "thumbnail" Size
link_type string "none" Link To
orderby_random boolean false Order by random

There is also a widget_media_gallery_instance_schema filter which can be used to add additional properties, such as a type for Jetpack’s Tiled Galleries. See #42285.

Theme Styling Updates

As with the previously-introduced media widgets, some themes will need to be updated to ensure the proper styling is applied to galleries that appear in the widget area context, since previously galleries would only appear in post content. Please follow #41969 for style changes that are made to the core bundled themes to then also make similar changes to your themes.


The gallery widget was first introduced and tested in the Core Media Widgets feature plugin. The plugin is developed on GitHub and the issues and pull requests related to the gallery widget can be reviewed there for a full history of the feature.

Please report new issues on Trac in the Widgets component, after first checking for any existing Gallery widget tickets.

#4-9, #dev-notes

Fixes to Text widget and introduction of Custom HTML widget in 4.8.1

The 4.8 release caused issues for many sites that had custom HTML in Text widgets, which until now had been common practice. So we’ve been working hard on fixes in the 4.8.1 release which aim to simultaneously serve the needs of novice users and advanced users alike: the rich Text widget (introduced in 4.8), a legacy mode for the Text widget, and a Custom HTML widget.

For more background on the changes in 4.8, see Addition of TinyMCE to the Text Widget. To review, the Text widget in 4.8 includes TinyMCE—the same visual editor used for writing post content—and it looks like:

Text Widget Legacy Mode

The issues with the introduction of TinyMCE to the Text widget revolve around the ways that TinyMCE attempts to clean up HTML code by deleting empty elements (such as those for dashicons) and dropping attributes it may not recognize (such as HTML5 Microdata attributes). Also with the 4.8’s removal of the “automatically add paragraphs” checkbox, there were also issues related to paragraphs and line breaks being added incorrectly.

Note that the Text widget was already designed to preserve the old behavior of the widget until it was modified and thus upgraded, so there are many instances of Text widgets in the wild today that could very well begin to break upon being modified. For this reason the issues were not reported right away and instead started to trickle in steadily after the release.

There were various solutions that were considered, but the one that had the consensus among contributors was:

[Check if the Text widget] was previously saved from an older version of WordPress before TinyMCE was added to the Text widget. If it is such a pre-existing Text widget instance, then use heuristics to detect if TinyMCE would negatively impact the contents of the widget, including the auto-p checkbox being unchecked, whether there are empty tags, and whether there are spandivscript, or style tags. When the Text widget is in this legacy mode, it can have a notice that informs users of the new HTML Code widget and that it should be used going forward. Likewise, in the new mode when TinyMCE is present, when the Text (HTML) tab is selected, there can be a note (perhaps an admin pointer) that encourages users to use the HTML Code widget instead. By implementing this, novice users with basic content in their widgets win, and advanced users with custom HTML content in their widgets will cease from being negatively impacted.

The Text widget in legacy mode looks the same as the Text widget before 4.8, but with the addition of a new notice:

The legacy mode will only be presented for widgets created prior to 4.8.0 that have instance data which match the logic in the WP_Widget_Text::is_legacy_instance() method. The legacy mode will not be presented to newly created Text widgets. Once a Text widget is opened and saved in legacy mode, it will permanently stay in legacy mode. There is a new instance property called “visual” which will be set to false when a widget is saved in legacy mode. When a new Text widget is created, it is opened in the default visual mode and the new instance will get saved with visual=true.

Text Widget Filters

There is a change in how the filter instance property was used in 4.8.0: in that release, when a Text widget was modified, the fact that it had been upgraded was stored by overloading the filter boolean property to also have the value of "content", indicating that the widget gets content filters applied like a post does. Since this string is a truthy value, I reasoned it would normally work the same in filters that check ! empty( $instance['filter'] ), but it would fail in cases where a plugin tried true === $instance['filter']. So 4.8.1 reverts the overloading of the filter property to again be a boolean, and this should improve compatibility for widget_text filters. Whenever a Text widget is modified with the default visual mode (with TinyMCE) it will get both visual=true and filter=true saved in its instance. When a Text widget is modified in the legacy mode, it will always get visual=false and its filter property will reflect the checked state of the auto-paragraph checkbox.

Another note on filters: special consideration was made for shortcodes in the Text widget given the frequency of plugins and themes adding shortcode support (since the widget does not recognized them by default in core). Plugins and themes have done add_filter( 'widget_text', 'do_shortcode' ) to add support. Since the widget_text filter applies before the new widget_text_content filter (as of 4.8), it will apply before wpautop will have applied, resulting in the possibility of extra line breaks being added undesirably if the shortcode output has new line characters. So to help prevent that from happening, the Text widget will temporarily move the do_shortcode handler from widget_text to widget_text_content just in time while the filters are being applied. See the relevant logic.

Help Pointers

For users who are accustomed to pasting HTML into the Text widget, when an attempt is made to paste markup into the visual editor a pointer will be displayed informing them that they should paste it into the Text tab instead, or to alternatively use the new Custom HTML widget (see section below):

Likewise, when a user opens the Text tab, it will also open a pointer to inform them of the Custom HTML widget:

While pointers are normally displayed on upgrades, these pointers will be displayed even on new installs since they reflect changes to long-standing behavior for the Text widget that users have become accustomed to. Any tutorials that instruct users to use the Text widget for pasting in arbitrary HTML should be updated to instruct the users to select the Custom HTML widget instead.

Custom HTML Widget

For advanced users or for any use case where arbitrary HTML needs to be displayed in a widget (such as a signup form or a 3rd party JavaScript widget), there is now a dedicated “Custom HTML” widget that is specifically for this purpose. It looks very similar to the classic Text widget, except it has a monospace font and it lacks the auto-paragraph checkbox:

Since users are prompted (per the pointers above) to try using the Custom HTML widget instead of the Text widget for some use cases, it is important that the widget content be able to be freely copied between the Text widget and the Custom HTML widget. For this reason, the Custom HTML widget retains the application of the widget_text filters like the Text widget does. The type of widget for which the filter is applying can be determined by looking at the type of the WP_Widget instance being passed as the last filter argument. When the widget_text filter is applied, it will pass the second $instance parameter in the same format as the Text widget, with title, text (instead of content), and filter and visual properties that are always both set to false (as if the instance was in legacy non-visual mode). In addition to re-applying the widget_text filter, the Custom HTML widget has a dedicated widget_custom_html_content filter whereas the the Text widget has a dedicated widget_text_content filter.

In addition to filter compatibility, the Custom HTML widget also tries to retain theme styling compatibility by using the same widget_text CSS class name on the outer widget wrapper and textwidget on the inner wrapper around the content itself. For any themes that wish to style the Custom HTML widget alone, there are the widget_custom_html and custom-html-widget class names used on the outer and inner wrapper elements respectively. For themes that wish to style the Text widget alone and exclude the Custom HTML widget, the :not() pseudo selector can be used, for example .widget_text:not(.widget_custom_html) and .textwidget:not(.custom-html-widget) for the outer and inner wrappers, respectively.

The markup generated by a Custom HTML widget on the frontend will look like:

<section id="custom_html-6" class="widget_text widget widget_custom_html">
  <h2 class="widget-title">My Title</h2>
  <div class="textwidget custom-html-widget">My Content</div>

This same Custom HTML widget’s instance data will look like:

  "title": "My Title",
  "content": "My Content"

For more specifics on the Custom HTML widget, refer to the subclass: WP_Widget_Custom_HTML.

Here is a list of tickets related to the Text widget and Custom HTML widget which are closed in the 4.8.1 release:

  • #40907: Introduce widget dedicated for HTML code
  • #40951: New Text Widget – Switching Between Visual/Text Editor Strips Out Code
  • #40960: Set `’filter’ => ‘content’` on starter content “business info” widget
  • #40960: Widgets: The Text widget should respect the “Disable the visual editor when writing” setting
  • #40972: TinyMCE editor in Text widget does not have RTL contents
  • #40974: Updated text widget do not save text (when using paste)
  • #40986: Widgets: text widget and media widgets cannot be edited in accessibility mode
  • #41021: Text widget does not show Title field or TinyMCE editor
  • #41158:  Increase tinymce panel z-index
  • #41361: Text widget can raise JS error if customize-base is enqueued on widgets admin screen
  • #41386: Text Widget – Wording – Legacy Mode 4.8.1 beta
  • #41392: Theme styles for Text widget do not apply to Custom HTML widget
  • #41394: Text widget: Rename legacy mode to visual mode and improve back-compat for widget_text filters


#4-8-1, #dev-notes, #tinymce, #widgets

WordPress 4.8 Field Guide

WordPress 4.8 is officially the best WordPress 2017 has seen!  Users will receive new and refined features focused on Editing and Customizing their sites while developers will be able to take advantage of 109 enhancements and features added.  Let’s look at the many improvements coming in 4.8…


Media Widgets

Not one, not two, but three new media widgets make their way into core. It’s like the AV crew just showed up and now the party can really begin. You get an audio widget. You get an image widget. You get a video widget. Check under your seat, media widgets for everyone!

Media Widgets for Images, Video, and Audio


Fabulously Rich Text Widget

Robin Hood has come to town and is handing out rich text editing to all the text widgets. No more having to manually type out your HTML in text widgets like it’s 2016.

Addition of TinyMCE to the Text Widget


Simpler Link Editing, Streamlined Browser Support, and Editor Instantiation via JS

Editing just got a lot easier, you can thank us later. But trust us that adding and managing links within the editor is now easier than ever. If you don’t trust us, then just try navigating in and out of links with your arrow keys. Pretty cool, huh? That’s the new TinyMCE inline element / link boundaries. As for the reduced browser support, let’s just say IE 8, 9, and 10 have been thanked for their service but can feel free to enjoy their retirement. Also, if bootstrapping the TinyMCE content editor dynamically via JS is your thing, then boy are you going to be excited!

Editor changes in 4.8

Editor API changes in 4.8


WMV and WMA get retirement packages

Remember Silverlight? Many browsers these days don’t. So the file formats which require the presence of the Silverlight plugin are being removed from core support. Files will still display as a download link, but will no longer be embedded automatically.

Removal of core embedding support for WMV and WMA file formats


Resizing the Customizer sidebar

If you have used the Customizer on a high-resolution screen, then you may have noticed that the Customizer sidebar is… suboptimally narrow. The sidebar is now variable width based on screen size to help you receive the best editing experience possible.

Customizer sidebar width is now variable


WordCamps and meetups all up in your admin dashboard

One of the best things about WordPress is its community. You can now read about WordCamps and meetups in your area right within your dashboard. No more excuses for missing the speaker or volunteer deadlines now!

Nearby WordPress Events

Showing upcoming local events in wp-admin


Mu will really love these multisite changes

We know you love superheroes but is_super_admin() needs to go. So while we’re replacing that with appropriate capabilities, we’re also treating you to some new hooks and a new $network_id parameter that gets wide usage across several functions.

Multisite Focused Changes in 4.8


Accessibility: saving the best for last

As a part of ongoing efforts to improve accessibility in WordPress, 4.8 includes some changes to headings within admin screens. This is a continuation of the work started in 4.3 on restoring the H1 (heading level 1) to the admin screens and continued in 4.4 with the introduction of a better headings hierarchy. Also improved is the Tag Cloud widget, now swapping out the title attributes in favor of aria-label attributes.

Cleaner headings in the admin screens

Tag Cloud widget changes in 4.8


But Wait, There is More!

Roughly 217 bugs, 108 enhancements, 1 feature request, and 16 blessed tasks have been marked as closed in WordPress 4.8. Some additional ones to highlight include:

  • REST API: orderby normalization (#38693)
  • REST API: Add supports object to /types response (#39033)
  • New filter to disable auto-focus on the login screen (#40301)
  • was added as an oEmbed provider (#38367)
  • HHVM removed from the test matrix on Travis (#40548)
  • Bundled Themes now support the new media and updated text widgets (#40745)
  • Popular plugins feed has been removed from the dashboard (#40702)
  • Support added for Bosnian locale (bs_BA) in remove_accents() (#39658)
  • Easily enqueue WP_Editor JavaScript files using the new wp_enqueue_editor() (#35760)

New Action Hooks

  • deleted_blog (#25584)
  • print_default_editor_scripts (#35760)

New Filter Hooks

  • file_mod_allowed replaces disallow_file_mods (#38673)
  • minimum_site_name_length (#39676)
  • nav_menu_submenu_css_class (#36163)
  • page_menu_link_attributes (#40359)
  • post_date_column_status (#39545)
  • signup_site_meta (#39223)
  • signup_user_meta (#39223)
  • wp_doing_cron (#39591)
  • widget_text_content (#40772)
  • rest_oembed_ttl (#40450)
  • widget_{$this->id_base}_instance (#32417)

Modified Filter Hooks

  • widget_text_content (#40772)
  • {$type}_template (#39525)
  • display_media_states (#39628)
  • media_library_show_audio_playlist (#31071)
  • media_library_show_video_playlist (#31071)
  • rest_pre_insert_comment (#39578)
  • wp_is_large_network (#40489)

External Library Updates

  • TinyMCE was updated from version 4.5.6 to version 4.6.2 (see: #40859).
  • Twemoji was updated from version 2.2.2 to version 2.3.0 (see: #40858).
  • zxcvbn was updated from version 1.0 to version 4.4.1 (see: #31647).

Please, test your code. That bears repeating: Please, test your code. Fixing issues now, before 4.8 is released, helps you and helps millions of WordPress sites. Please. Test. Your. Code.

#4-8, #dev-notes, #field-guide

Media Widgets for Images, Video, and Audio

As first introduced in the Image Widget Merge Proposal, WordPress 4.8 includes media widgets (#32417) for not only images (#39993) but also video (#39994) and audio (#39995), on top of an extensible base for introducing additional media widgets in the future, such as for galleries and playlists. To quote [40640]:

The last time a new widget was introduced, Vuvuzelas were a thing, Angry Birds started taking over phones, and WordPress stopped shipping with Kubrick. Seven years and 17 releases without new widgets have been enough, time to spice up your sidebar!

Since widgets are a very old part of WordPress (since 2.2), widgets in core have been very much entirely built using PHP with some Ajax sprinkled on top. In the time since WP_Widget was introduced in 2.8, WordPress has made dramatic shifts toward developing interfaces in JavaScript, including with the Customizer in 3.4 and the Media Library in 3.5, and more recently with the focus on the REST API and the editor (Gutenberg).

Given that the media widgets are naturally interfacing with the media library JS, it is necessary that the media widgets make use of JavaScript to construct their UI instead of relying on PHP. The media widgets fully reuse the existing media modal frames for not only selecting the media to display but also to edit all of its properties: attachments can be selected from the media library, while external media can be inserted by URL.

Initial groundwork for shimming JavaScript into widgets was added in 3.9 via the widget-added and widget-updated events, which are also being utilized in 4.8 with the Addition of TinyMCE to the Text Widget. A more recent proposal for making JavaScript more of a first class citizen can be found in #33507 and the media widgets incorporate some of its patterns that were also prototyped in the JS Widgets plugin. The media widgets make use of a Backbone View to manage the widget control’s UI and a Backbone Model for reading and manipulating the widget instance data.

Base Media Widget

PHP: wp-includes/widgets/class-wp-widget-media.php
JS: wp-admin/js/widgets/media-widgets.js

The three widgets all extend a WP_Widget_Media PHP abstract class; in JS the Backbone view wp.mediaWidgets.MediaWidgetControl and wp.mediaWidgets.MediaWidgetModel are extended. A unique aspect of how the media widgets work is how instance data is validated and sanitized. Normally widgets utilize procedural code to sanitize instances via a subclassed WP_Widget::update() method. The media widgets, however, make use of a REST API schema returned from WP_Widget_Media::get_instance_schema() to sanitize instances declaratively. The WP_Widget_Media::update() method iterates over the schema and uses it to sanitize and validate the instance properties. (Adding schemas to the base WP_Widget class is also proposed in #35574.) The JSON schema is extended to include a couple custom properties: media_prop provides the media JS property name that the widget instance maps, and the should_preview_update flag indicates whether a change to that prop should cause the control preview to re-render (this would be retired with a React rewrite and/or leveraging of corresponding blocks in Gutenberg).

The WP_Widget_Media abstract class has one abstract method which subclasses must implement: WP_Widget_Media::render_media(). This is the method that WP_Widget_Media::widget() calls with the $instance props to render the media for a given widget media type. Before the props are passed to the method, the $instance is applied through  widget_{$id_base}_instance filters.

A media widget subclass should output additional JS templates as the control needs by extending WP_Widget_Media::render_control_template_scripts(). Scripts that the widget control requires can be enqueued by extending WP_Widget_Media::enqueue_admin_scripts(); this is also where the PHP exports can be done with calls to wp_add_inline_script().

The REST API schema is exported from PHP to JS on the subclassed MediaWidgetModel prototypes. Other properties on the prototypes which should be extended include l10n and mime_type. The model subclasses are registered by assigning them to the wp.mediaWidgets.modelConstructors object, keyed by the widget ID base (e.g. media_image, media_video, etc). In the same way, the Backbone View wp.mediaWidgets.MediaWidgetControl is subclassed and registered by adding to the wp.mediaWidgets.controlConstructors object, also keyed by widget ID base. This is similar to how control types are registered in the Customizer.

The MediaWidgetControl and MediaWidgetModel will be instantiated once a widget control is expanded. Their instances will be then added to the wp.mediaWidgets.widgetControls object and wp.mediaWidgets.modelCollection respectively.

There is a subclass of a media controller and a couple media views in the wp.mediaWidgets namespace. These extensions to media classes are needed due to current limitations in media extensibility. They may be removed/reduced with improvements in #40427.

As with the incorporation of TinyMCE into the Text widget, the incorporation of the media library into the media widget has necessitated constructing the widget’s form fields differently than how they are normally done. Widgets in core have historically utilized static HTML for their control form fields. Every time a user hits “Save” the form fields get sent in an Ajax request which passes them to the WP_Widget::update() method and then the Ajax response sends back the output of WP_Widget::form() which then replaces the entire form. (Note widgets in the Customizer behave differently since there is no Save button in the widget, as updates are synced and previewed as changes are made; read more about Live Widget Previews.) This worked for static HTML forms in the past, but in the case of the media widgets the UI built with JavaScript instead of server-side PHP.

To avoid having to rebuild the media preview every time the user hits Save on the admin screen, the media widget puts its UI elements outside of the container that is “managed” by the server which gets replaced with each save. (A similar approach has also been employed by the new TinyMCE-extended Text widget in 4.8.) Since core does not yet represent a widget’s state in a JavaScript model (again see #33507), the media widget syncs its MediaWidgetModel props with hidden inputs that get rendered by WP_Media_Widget::form() in order to be sent to the server for preview and saving. The container for the media widget’s fields is .media-widget-control and the traditional container for a widget’s input fields as rendered by WP_Widget::form() is .widget-content:

For examples of how to implement media widgets, see the three implementations included in 4.8 as follows.

Image Widget

PHP: wp-includes/widgets/class-wp-widget-media-image.php
JS: wp-admin/js/widgets/media-image-widget.js

Field Type Default Description
attachment_id integer 0 Attachment post ID
url string "" URL to the media file
title string "" Title for the widget
size string "medium" Size
width integer 0 Width
height integer 0 Height
caption string "" Caption
alt string "" Alternative Text
link_type string "none" Link To
link_url string "" URL
image_classes string "" Image CSS Class
link_classes string "" Link CSS Class
link_rel string "" Link Rel
link_target_blank boolean false Open link in a new tab
image_title string "" Image Title Attribute

Video Widget

PHP: wp-includes/widgets/class-wp-widget-media-video.php
JS: wp-admin/js/widgets/media-video-widget.js

The video widget allows for embeddable video formats to be selected from the media library or linked to externally by URL. In addition, URLs to YouTube or Vimeo may also be provided since the video shortcode logic supports rendering them via MediaElement.js. It is possible for plugins to add support for additional oEmbed providers via extending the video widget control’s isHostedVideo method; for more, see the Jetpack PR for adding VideoPress support.

Field Type Default Description
attachment_id integer 0 Attachment post ID
url string "" URL to the media file
title string "" Title for the widget
preload string "metadata" Preload
loop boolean false Loop
content string "" Tracks (subtitles, captions, descriptions, chapters, or metadata)
mp4 string "" URL to the mp4 video source file
m4v string "" URL to the m4v video source file
webm string "" URL to the webm video source file
ogv string "" URL to the ogv video source file
flv string "" URL to the flv video source file

Note that the presence of format-specific fields is dependent on what is returned by wp_get_video_extensions().

Audio Widget

PHP: wp-includes/widgets/class-wp-widget-media-audio.php
JS: wp-admin/js/widgets/media-audio-widget.js

The audio widget allows for embeddable audio formats to be selected from the media library or linked to externally by URL. Note that there are no oEmbed audio formats supported since the audio shortcode logic only supports rendering players for actual audio files.

Field Type Default Description
attachment_id integer 0 Attachment post ID
url string "" URL to the media file
title string "" Title for the widget
preload string "none" Preload
loop boolean false Loop
mp3 string "" URL to the mp3 audio source file
ogg string "" URL to the ogg audio source file
m4a string "" URL to the m4a audio source file
wav string "" URL to the wav audio source file

Note that the presence of format-specific fields is dependent on what is returned by wp_get_audio_extensions().

Default Themes Updates

Themes that add custom styles to the MediaElement.js player (namely Twenty Thirteen and Twenty Fourteen) were updated from just styling it within syndicated content, to also include instances within widgets. Most themes don’t restrict styles for captioned images or media players to just post content, that is, limit CSS selectors to classes output by post_class(). If your theme does, make sure to either remove that constraint or include a .widget selector.


The work on the new media widgets in core was conducted in the Core Media Widgets plugin and on its corresponding wp-core-media-widgets GitHub repo. Many of the decisions that were made in the architecture of the feature can be found there in the GitHub issues and pull requests.

Keep in mind that the media widgets will likely undergo many more changes with the incorporation of the Gutenberg editor, and that widgets themselves will likely see many changes to align with Gutenberg’s editor blocks which are now being prototyped. Essentially if you can insert a given type of block into the editor, there should also be a widget available for representing the same content.

#4-8, #dev-notes, #media-widgets

Tag Cloud widget changes in 4.8

The Tag Cloud widget is still pretty popular and for a number of years, it has used title attributes to visually display the number of posts using a specific tag.

tag cloud in WordPress 4.7

Example of a title attribute displaying the number of items for a specific tag in WordPress 4.7

WordPress 4.8 removes these title attributes and replaces them with aria-label attributes with optional counts displayed in plain text.

Why it matters

Title attributes aren’t very accessible. Depending on the specific assistive technology and on user settings, they might be completely ignored. On touch devices, title attributes are a bit pointless.

The best option is not to rely on title attributes to convey important information to users. Information that is important enough should be available to all users.

In the last few releases, WordPress has been progressively removing many title attributes used in the admin screens (see: #24766). The same principle applies to the front end. The Tag Cloud widget is another step towards the progressive removal of title attributes in the front end where they’re used inappropriately.

What plugin or theme authors should know

There are no visual changes by default, other than the removal of the title attributes. There is a new option for developers though: tag counts can be displayed in plain text within the list of tags. Users will now find a checkbox in the widget interface, consistent with what other widgets already do (e.g., Archives and Categories widgets).

the tag cloud widget admin interface

The new option “Show tag counts” in the widget admin interface

Checking the “Show tag counts” option in the admin will make the Tag Cloud show the counts alongside each tag. The screenshot below compares the Tag Cloud with and without the counts displayed:
the Tag Cloud with and without tag counts

Themes can style the tag counts targeting the new CSS class tag-link-count. Also, note that wp_generate_tag_cloud() has a new show_count argument. Themes and plugins that filter the Tag Cloud arguments can use this new argument to force the tag counts to be displayed.

Theme authors are recommended to check how the tag counts look like in their themes and make small CSS adjustments if needed. And remember, the tag counts are not displayed by default! Users can enable and also disable them at any time.

Some technical details

The relevant changeset is [40816] and the related ticket is #35566. Behind the scenes, the tag cloud now attempts to determine whether to output the aria-label attribute, trying to respect the theme author’s intent.

By default, the Tag Cloud displays tags in different font sizes to visually represent how many times they’re used. When tags have a different font size, they visually convey important information that should also be available to assistive technologies.

Sometimes instead, theme authors set up the Tag Cloud to display all tags with the same font size. Twenty Sixteen, for example, displays all the tags with a “flat” styling. It does that by filtering the arguments passed to wp_generate_tag_cloud() and setting the smallest and largest arguments to the same value (note: this is the recommended way to output “flat” tags).

In order to always serve the same information to all users and try to respect the theme author’s intent, the aria-label that conveys the count information to assistive technologies gets printed out:

  • when tags have a different size
  • when the tag count is displayed in plain text (for example when users check the checkbox in the Tag Cloud widget), regardless of the tags font size

A quick update about title attributes

To give an idea of the progress done so far, here’s data from the WordPress codebase from version 4.0 to 4.7. Searching for occurrences of  title= (space-title-equal) only within .php files and only in the wp-admin directory:

WordPress 4.0: 157 results found in 37 files
WordPress 4.1: 156 results found in 37 files
WordPress 4.2: 146 results found in 35 files
WordPress 4.3: 101 results found in 30 files
WordPress 4.4: 97 results found in 32 files
WordPress 4.5: 21 results found in 12 files
WordPress 4.6: 19 results found in 11 files
WordPress 4.7: 17 results found in 9 files

Of the 17 title attributes in WordPress 4.7, four of them are legitimate because they’re used on <iframe> elements and most of the other ones are in files no longer used by core. Many of these occurrences were within loops so the number of title attributes actually output was higher, yet going from 157 down to a very few is wonderful progress.

As always, any comments and contribution to improving accessibility are welcome!

#4-8, #accessibility, #dev-notes