I cross-posted much of this post to Make WordPress Core before WordPress 4.0 Beta 1. I’ll be updating this version with more examples throughout the beta period.
WordPress 4.0 features several new additions to the Customizer API (see also Theme Customization API). In this post, I’ll discuss the improvements in detail.
Customizer Panels
The Customizer now includes a new way to group options together: panels. Just like a section is a container for controls, a panel is a container for sections. This was implemented in #27406. In WordPress Core, widget management in the customizer has been consolidated into the “Widgets” panel. Instead of having as many sections as there are widget areas making a mess out of the customization experience, only one widgets section appears alongside the other customizer sections. But when you select it, rather then sliding down like regular sections, it slides over to reveal a new context for the entire customizer controls area. My Menu Customizer project also relies on this new panels functionality, as all menus can be consolidated into one panel. But themes and/or plugins with extensive options may also wish to create their own panels to better organize their controls, so we built an API for customizer panels. The Panels API is easy-to-use and works almost identically to the existing customizer section API. Add a panel with the following, within the customize_register
action:
$wp_customize->add_panel( 'panel_id', array( 'priority' => 10, 'capability' => 'edit_theme_options', 'theme_supports' => '', 'title' => '', 'description' => '', ) );
As always, only use the arguments where you aren’t using the default values. Note that the panel will not be displayed unless sections are assigned to it. To add a section to a panel, just use the panel id for the new panel
argument:
$wp_customize->add_section( 'section_id', array( 'priority' => 10, 'capability' => 'edit_theme_options', 'theme_supports' => '', 'title' => '', 'description' => '', 'panel' => 'panel_id', ) );
You may notice that $wp_customize->add_panel
and $wp_customize->add_section
have the same arguments (other than panel, of course). This is because panels are a special type of section; technically speaking, WP_Customize_Panel
extends WP_Customize_Section
. Your sections are backwards-compatible: you can add the panel argument to existing sections without issues. However, you do need to check for the existence of WP_Customize_Manager->add_panel()
if you’re maintaining pre-4.0 compatibility. As with Customizer Sections, you can access and modify Panels via:
$wp_customize->get_panel( $id );
$wp_customize->remove_panel( $id );
New Built-in Customizer Controls
WordPress core now provides support for a much wider array of Customizer controls. Implemented in #28477, these changes eliminate the need to create custom controls for most common use cases. The textarea
type is now supported in core. For any type of input that uses an input element, you can simply specify the type
attribute using the type
parameter of $wp_customize->add_control()
. Here’s an example:
$wp_customize->add_control( 'setting_id', array( 'type' => 'url', 'priority' => 10, 'section' => 'title_tagline', 'label' => 'URL Field', ) );
Which results in the following markup in the Customizer:
<li id="customize-control-setting_id" class="customize-control customize-control-url"> <label> <span class="customize-control-title">URL Field</span> <input type="url" value="" data-customize-setting-link="setting_id"> </label> </li>
This is pretty powerful, as you can now use the built-in WP_Customize_Control
for most common use-cases rather than creating a custom control. But what about input types like number
and range
that require additional attributes like min
, max
, and step
?
New Built-in Customizer Control Parameters
First of all, all of the built-in Customizer controls (including the custom controls such as WP_Customizer_Color_Control
) now support descriptions, just like Customizer sections have descriptions (see #27981). This was much-needed and allows for inline help text at the control level. More interestingly, to complement the new support for arbitrary input types, a new input_attrs
parameter allows you to add attributes to the input
element (also implemented in #28477). This extends beyond just using min
, max
, and step
for number
and range
, to the ability to add custom classes, placeholders, the pattern attribute, and anything else you need to the input element. Here’s an example:
$wp_customize->add_control( 'setting_id', array( 'type' => 'range', 'priority' => 10, 'section' => 'title_tagline', 'label' => 'Range', 'description' => 'This is the range control description.', 'input_attrs' => array( 'min' => 0, 'max' => 10, 'step' => 2, 'class' => 'test-class test', 'style' => 'color: #0a0', ), ) );
Which results in the following markup in the Customizer:
<li id="customize-control-setting_id" class="customize-control customize-control-range"> <label> <span class="customize-control-title">Range</span> <span class="description customize-control-description">This is the range control description.</span> <input type="range" min="0" max="10" step="2" class="test-class test" style="color: #0a0;" value="" data-customize-setting-link="setting_id"> </label> </li>
Which displays as follows (in Chrome 35):
The ability to add classes is particularly useful if you need to target specific controls with CSS or JS, but it doesn’t need to have any special markup. I’m using this in the Menu Customizer for the Menu Name field, which is just an ordinary text control with a special setting type.
Contextual Controls
Customizer controls can now be displayed or hidden based on the Customizer’s preview context. For example, options that are only relevant to the front page can be shown only when the user is previewing their front page in the Customizer (see #27993). This is already implemented in core for Widgets; Widgets have always been contextually faded and shown/hidden based on their visibility in the preview, but this functionality is now built off of the core active_callback
API in both PHP and JS. There are three different ways to specify whether a given control should only be displayed in a certain context. The first, and most straightforward, is to use the active_callback
argument in $wp_customize->add_control()
.
$wp_customize->add_control( 'front_page_greeting', array( 'label' => __( 'Greeting' ), 'type' => 'textarea', 'section' => 'title_tagline', 'active_callback' => 'is_front_page', ) );
Note that you may use either built-in conditional functions or a custom function. If you have a custom control (via a subclass of WP_Customize_Control
) and a custom callback function, you can skip the active_callback
argument and override the active_callback
function instead:
class WP_Greeting_Control extends WP_Customize_Control { // ... function active_callback() { return is_front_page(); } }
Finally, the customize_control_active
filter will override all of the other active callback options and may be a better solution in certain cases (note that this particular example will be avoidable with future work on expanding the Customizer’s JS API, and does not hide the title_tagline
section, only the controls in it):
function titletagline_customize_control_active_filter( $active, $control ) { if ( 'title_tagline' === $control->section ) { $active = is_front_page(); } return $active; } add_filter( 'customize_control_active', 'titletagline_customize_control_active_filter', 10, 2 );
In addition to the PHP API for contextual controls, you can override the control-visibility-toggle function on the JS side. By default, controls will slideUp
and slideDown
as they become visible or hidden when the Customizer preview is navigated. If you’re familiar with the Customizer control JS API (see wp-admin/js/customize-controls.js
, and wp.customize.Control
), the Widgets implementation of a custom toggle function is a good example:
api.Widgets.WidgetControl = api.Control.extend({ // ... /** * Update widget control to indicate whether it is currently rendered. * * Overrides api.Control.toggle() * * <a href='http://profiles.wordpress.org/param' class='mention'>@param</a> {Boolean} active */ toggle: function ( active ) { this.container.toggleClass( 'widget-rendered', active ); }, // ... ) }; /** * Extends wp.customize.controlConstructor with control constructor for widget_form. */ $.extend( api.controlConstructor, { widget_form: api.Widgets.WidgetControl, } );
Changes to the customize_update_ and customize_preview_ Actions
You probably already know that the Customizer supports both option and theme_mod types for settings. But did you know that you can register arbitrary types? Since this is generally undocumented, I’ll show how it works (this has been in place since 3.4):
$wp_customize->add_setting( 'setting_id', array( 'type' => 'custom_type', 'capability' => 'edit_theme_options', 'theme_supports' => '', 'default' => '', 'transport' => 'refresh', 'sanitize_callback' => '', 'sanitize_js_callback' => '', ) );
There are a few actions that you can use to handle saving and previewing of custom types (option
and theme_mod
are handled automatically). Namely, customize_update_$type
and customize_preview_$type
are useful here. Previously, the value of the setting was passed to these actions, but there was no context. In 4.0, via #27979, the WP_Customize_Setting
instance is passed to these actions, allowing more advanced saving and previewing operations. Here’s an example from my Menu Customizer project:
function menu_customizer_update_menu_name( $value, $setting ) { ... // Update the menu name with the new $value. wp_update_nav_menu_object( $setting->menu_id, array( 'menu-name' => trim( esc_html( $value ) ) ) ); } add_action( 'customize_update_menu_name', 'menu_customizer_update_menu_name' );
This part of the Customizer API is a bit too complex to fully explain here, as most of it already existed, but suffice to say that the addition of the setting instance to these actions greatly expands the possibilities of working with custom setting types in the Customizer.
New “customize” Capability
Note: this API component is coming in 4.0 beta 2.
The Customizer has been decoupled from edit_theme_options
in favor of a customize
meta capability, which is mapped to edit_theme_options
by default. This allows for wider use of the Customizer’s extensive capability-access options, which are built into panels, sections, and settings. Additionally, this makes it possible to allow non-administrators to use the customizer for, for example, customizing posts. This change is an important step to expanding the scope of the Customizer beyond themes. See #28605. To allow users with lower capabilities to access the Customizer, use something like the following:
function allow_users_who_can_edit_posts_to_customize( $caps, $cap, $user_id ) { $required_cap = 'edit_posts'; if ( 'customize' === $cap && user_can( $user_id, $required_cap ) ) { $caps = array( $required_cap ); } return $caps; } add_filter( 'map_meta_cap', 'allow_users_who_can_edit_posts_to_customize', 10, 3 );
Customizer Conditional Function
The new is_customize_preview()
conditional function can be used to check whether the front-end is being displayed in the Customizer. The naming derives from the fact that the term “preview” applies to both theme previews and previewing changes before saving them. See #23509 for some sample use-cases from WordPress.com.
Examples
Look at the Widget Customizer and Menu Customizer code for more examples of many of these features. Please feel free to ask in the comments if you have you questions with any of these implementations; I spent a good amount of time preparing and reviewing the associated patches and soliciting feedback in the process of adding these changes to core.
Use This API for Good, not Evil
These new additions to the Customizer API greatly expand the ability to create intricate options for users. But please don’t forget about WordPress’ core philosophies. So, before you go adding entire panels full of new options, remember to strive for simplicity and design for the majority, even in themes and plugins. These additions make the Customizer better for developers; now, let’s make it as good as it can be for our end-users.