Getting Started with the WordPress Customizer

Avatar of Scott Fennell
Scott Fennell on

The following is a guest post by Scott Fennell, a WordPress theme & plugin developer in Anchorage, AK and a regular contributor around here.

Let’s say you have a client whose business is large enough to have several departments. Now let’s say that this client wants each of their departments to have their own website on a dedicated domain. Each site is to have the same layout, but a different color scheme. This is a phenomenal use-case for the WordPress Customizer (aka the Theme Customization API), and I’d like to share a basic example of how to build it into a theme.

By the end of this article, we will have a WordPress theme that has a couple of theme modifications, with an obvious pattern in our code for adding more down the road.

Grab the Example

The example theme we’ll be citing is in GitHub, named CSS-Tricks Theme Mod Demo. You can also clone it and run it on your WordPress install—it works right out of the box.

A Quick Note on Terminology

The terms Customizer, Theme Modification API, and Theme Customization API have been used somewhat interchangeably in the WordPress community for a while now. I think some subtle differences have emerged in those terms, which I’ll try to respect.

  • Customizer refers to the actual UI in your WordPress install at /wp-admin/customize.php.
  • Theme Modificaton API refers to the family of functions that CRUD the data that the Customizer manages.
  • Theme Customization API refers to the family of functions that developers can use to add or remove settings to/from the Customizer.

All three of these concepts will be covered in this article, though I’ll prefer the phrase Theme Mods when naming things, such as my theme folder. Personal preference, no big deal.

Make a Test Site

Make sure your test install of WordPress is backed up and running the latest version, which at time of writing is 4.3.2. In fact, a fresh install is all we need: no plugins, no nothing. Only the example theme from above, installed and activated alone.

A screenshot of themes.php, showing that our theme is active.

With our sample theme installed, check out wp-admin: You’ll notice that under the themes menu on the left, there’s a link labeled Customize. That menu item exists by default; I did not have to register it or declare support for it. Even if our theme did not register any theme mods, that link would still be useful because WordPress core adds some settings there automatically for things like site title, site tagline, etc. Customize is what we’re here to do, so click on that link!

The Customizer Panel

Here we are in the customizer. It’s organized into a hierarchy containing three layers: panels contain sections, sections contain settings, and settings are the data that gets managed by the controls in the customizer UI.

We might visualize it like this:

Panel
|__ Section
|   |__ Setting and its Control
|   |__ Setting and its Control
|__ Section
|   |__ Setting and its Control
|   |__ Setting and its Control
|
Panel
|__ Section
|   |__ Setting and its Control
|   |__ Setting and its Control
|__ Section
    |__ Setting and its Control
    |__ Setting and its Control

This hierarchical situation is a really nice building block for understanding the customizer. If you’re not clear on it, take a moment to browse the documentation related to panels, versus sections, versus settings and their controls. My learning curve would have been much faster if I had learned this early on.

Another tripping hazard: If you’re working your way into understanding the customizer, you might first attempt to register a panel, and then check your /wp-admin/customizer.php to see if it worked before registering sections and settings for it. Don’t bother: A panel will not show unless there are already settings and their corresponding controls registered for it.

Let’s walk through the hierarchy in more detail.

Panels

Immediately, we see on the left that we have two panels, called Site Identity and Body.

A screenshot from the customizer, showing our panels.

The Site Identity panel ships with WordPress, whereas our theme added the Body panel.

Sections

If we click on Body, we’ll see that it carries some sections: Colors and Layout Options. Again, just to reiterate, I have code in this theme that added the Body panel and in turn, I also have code that added the Colors and Layout Options sections to the Body panel.

A screenshot showing the sections that my theme added to the customizer.

Settings

If we click on Colors, we’ll see that I’ve registered a couple of settings, along with their controls, to edit the color of our theme. When doing so, I supplied default values of orange and black, which are pre-populated here in the controls on the left.

A screenshot showing some of the settings that my theme added to the customizer.

A word on data: WordPress saves and retrieves settings via the Theme Modification family of functions. We can use those functions in our theme code as well, though get_theme_mod() is the only one I’ll be using in this theme.

Controls

I’m telling you that panels, sections, and settings have a hierarchical relationship. Controls are part of this family too: Think of a control as a sibling of each setting.

A control is what draws the UI in the customizer for a given setting. We have to register both a control and a setting for a theme mod in order to do anything useful with that theme mod.

Note: You can go really bonkers and register all kinds of custom control types. Things like checkbox groups, ranges, multi-selects. Credit Justin Tadlock for providing many inspiring examples of this, sometimes using JavaScript templating. Even though we have to register a control for each setting, we don’t need to register any custom control types for this tutorial, and I won’t be digging into that. A number of awesome control types ship with WordPress, including the color picker in the example above. You can read more about the control types that ship with WordPress in the codex.

WordPress Defaults and How to Remove Them

Let’s drill back up to the customizer’s top-level panels, and then click on the Site Identity panel. WordPress adds this panel by default, skips the section layer, and goes straight to a couple of settings for the site title and site icon. The site icon is new, but trust me, it’s part of core.

Screenshot of the settings that WordPress adds to the customizer by default.

You might expect to also see a setting for site tagline here, and normally you would, but I removed it. In fact, I removed a bunch of panels, sections, and settings that WordPress adds by default, just to provide an example of how to deregister them. Our example theme carries a class called CSST_TMD_Customizer, which is where I’m interacting with the customizer to customize these theme mods. In order to remove the default nodes, I hook into the customize_register action and access the $wp_customize object.

<?php

class CSST_TMD_Customizer {

  public function __construct() {

    ...
  
    // Strip away unused customizer nodes.
    add_action( 'customize_register', array( $this, 'deregister' ), 999 );

  }

  ...
  
  /**
   * Remove stuff that WordPress adds to the customizer.
   *
   * param object $wp_customize An instance of WP_Customize_Manager.
   */
  public function deregister( $wp_customize ) {

    // Remove the setting for blog description, AKA Site Tagline.
    $wp_customize -> remove_control( 'blogdescription' );

    // Remove the section for designating a static front page.
    $wp_customize -> remove_section( 'static_front_page' );

    // Remove the panel for handling nav menus.
    $wp_customize -> remove_panel( 'nav_menus' );     

  }

?>

I chose to remove these items because they provide a good example of the hierarchy we’ve been discussing. I was able to determine what key to pass to each remove_ function by looking at the markup for that area in the customizer. It’s usually found as the data attribute for data-customize-setting-link for that control (you can see this in the screenshot above).

Do pardon me: removing nav_menus seems to trigger a PHP warning in debug mode. I haven’t been able to find a way to avoid this. If that’s bothersome, by all means, leave them in and enjoy your newfound ability to manage nav menus in the customizer.

Defining Our Own Panels, Sections and Settings

I don’t like to repeat myself when I write code, so I’m going to define our panels, sections, and settings just once in a huge array that the rest of our theme can loop through. This will set us up to do things like add those settings to the customizer and output them as CSS on the front end.

The example theme carries a class called CSST_TMD_Theme_Mods where I create an array of panels, sections, and settings to add to the customizer. This class is really the point of this article, and I hope you take a moment to read the source. Let’s walk through an example where the goal is to add the body panel, add the color section to that panel, and then add the background_color setting to that section. In other words:

Panel: Body
|__ Section: Color
    |__ Setting: Background Color, controlled with a Color Picker

First, a panel named body is defined:

<?php

class CSST_TMD_Theme_Mods {

  /**
   * Define our panels, sections, and settings.
   * 
   * @return array An array of panels, containing sections, containing settings.
   */
  function get_panels() {

    ...
    
    // Start an annoyingly huge array to define our panels, sections, and settings.
    $out = array();

    // Define the body panel.
    $body = array(

      // The title for this panel in the customizer UI.
      'title' => esc_html__( 'Body', 'csst_tmd' ),
      
      // The description for this panel in the customizer UI.
      'description' => esc_html__( 'Theme Mods for the Page Body', 'csst_tmd' ),
        
      // The order within the customizer to output this panel.
      'priority' => 20,
        
      // The body panel has a bunch of sections.
      'sections' => array(),

    );
    $out['body'] = $body;

Next, a section called colors is added to the body panel:

    // Define the colors section, which resides in the body panel.
    $out['body']['sections']['colors'] = array(

    // The title for this section in the customizer UI.
    'title' => esc_html__( 'Colors', 'csst_tmd' ),

    // The description for this section in the customizer UI.
    'description' => esc_html__( 'Colors for the Page Body', 'csst_tmd' ),
      
    // The order within this panel to output this section.
    'priority' => 10,
      
    // The colors section has a bunch of settings.
    'settings' => array(),

  );

?>

Then, a setting for background_color is added to the colors section:

<?php

    // The setting for body background color.
    $out['body']['sections']['colors']['settings']['background_color'] = array(

      // The type of control for this setting in the customizer.
      'type' => 'color',

      // The header text for the control.
      'label' => esc_html__( 'Body Background Color', 'csst_tmd' ),

      // The descriptive text for the control.
      'description' => esc_html( 'The background color for the body element, on landscape screens smaller than 800px.', 'csst_tmd' ),
      
      // The order within this section for outputting this control.
      'priority' => 10,

      // The default value for this setting.
      'default' => '#000000',

      // A callback function for sanitizing the input.
      'sanitize_callback'    => 'sanitize_hex_color',
      'sanitize_js_callback' => 'sanitize_hex_color',

      // Do we want to use css from this setting in TinyMCE?
      'tinymce_css' => FALSE,

      // Is this setting responsible for creating some css?
      'css' => array(
        
        // This array amounts to one css rule. We could do several more right here.
        array(

          // Here's the selector string.
          'selector' => 'body',

          // Here's the css property.
          'property' => 'background-color',
      
          // Here are some media queries for this css.    
          'queries' => array(
            'max-width'   => '800px',
            'orientation' => 'landscape',
          ),

        // End this css rule. We could start another one right here, perhaps to use this setting for a border-color on the body element, or whatever.         
        ),

      // End the list of css rules (yeah, there's just one right now).
      ),
    
    // End this setting.
    );

?>

This makes for a lot of nesting, and a really long array definition, which I’ll admit can be tough to follow. I tend to work in deep arrays anyway, and this technique has held up well for me. In implementations with lots of settings, I’ll often use transients to avoid any performance concerns from all the loops. I’ll also break the array up into multiple functions for each panel to make it easier to read and debug.

The good news is that you don’t have to do it this way! There’s nothing in this class that hooks into WordPress in any specific format. This is just me, hanging out, defining my settings in a way that works well for me. You could get even more nuanced with media queries. You could perhaps scope a rule to only apply when certain body classes are present. You could eschew my massively multidimensional array and declare your settings one at a time. Be as creative and powerful, or as simple and readable, as you want. It’s totally up to you.

If all of my loops make you dizzy, see the excellent Twenty Sixteen theme, which registers its customizer settings without looping. I tend to shy away from their technique because I find it to be less DRY. For example, the string color_scheme appears 83 times in that file.

I can imagine another approach, inspired by the work and writing of Tom McFarline. He has a long series of posts about using object-oriented programming (OOP) to manage WordPress settings. If that kind of architecture seems more intuitive to you, go for it. I think you’ll find that his OOP work on the Settings API has some obvious parallels that would also work well for the Customizer API.

Regardless of the technique that’s chosen, this information still has to be passed into the customizer in order to get a UI for these settings. We haven’t done that yet, so let’s do it!

Passing Theme Mods to the Customizer

Adding our settings to the customizer is very similar to removing core settings from the customizer. Again, we have to hook into customizer_register, only this time we’re adding stuff instead of removing it. Here’s a snippet from the function that does that:

<?php

class CSST_TMD_Customizer {

  public function __construct() {

    // Register our custom settings and controls.
    add_action( 'customize_register' , array( $this, 'register' ), 970 );

    ...

  }

  /**
   * Add our panels, sections, and settings to the customizer.
   * 
   * @param object $wp_customize An instance of the WP_Customize_Manager class.
   */
  public function register( $wp_customize ) {
    
    // Fire up our theme mods class.
    $theme_mods_class = new CSST_TMD_Theme_Mods;

    // Grab our panels, sections, and settings.
    $panels = $theme_mods_class -> get_panels();

    // For each panel...
    foreach ( $panels as $panel_id => $panel ) {

      // Add this panel to the UI.
      $wp_customize -> add_panel(
        $panel_id,
        array(
          'title'       => $panel['title'],
          'description' => $panel['description'],
          'priority'    => $panel['priority'],
        )
      );

      // For each section in this panel, add it to the UI and add settings to it.
      foreach( $panel['sections'] as $section_id => $section ) {

        // Add this section to the UI.
        $wp_customize -> add_section(
          $panel_id . '-' . $section_id,
          array(
            'title'       => $section['title'],
            'description' => $section['description'],
            'priority'    => $section['priority'],
            'panel'       => $panel_id,
          )
        );

        // For each setting in this section, add it to the UI.
        foreach( $section['settings'] as $setting_id => $setting ) {

          // Start building an array of args for adding the setting.
          $setting_args = array(
            'default'              => $setting['default'],
            'sanitize_callback'    => $setting['sanitize_callback'],
            'sanitize_js_callback' => $setting['sanitize_js_callback'],
          );

          // Register the setting.
          $wp_customize -> add_setting(
            $panel_id . '-' . $section_id . '-' . $setting_id,
            $setting_args
          );
          
          // Start building an array of args for adding the control.
          $control_args = array(
            'label'       => $setting['label'],
            'section'     => $panel_id . '-' . $section_id,
            'type'        => $setting['type'],
            'description' => $setting['description'],
          );

          // Settings of the type 'color' get a special type of control.
          if( $setting['type'] == 'color' ) {

            $wp_customize -> add_control(
              
              // This ships with WordPress. It's a color picker.
              new WP_Customize_Color_Control(
                $wp_customize,
                $panel_id . '-' . $section_id . '-' . $setting_id,
                $control_args
              )
            
            );

          // Else, WordPress will use a default control.
          } else {

            $wp_customize -> add_control(
              $panel_id . '-' . $section_id . '-' . $setting_id,
              $control_args
            );

          }

        // End this setting.
        }

      // End this section.
      }

    // End this panel.
    }

  }

?>

We’re looping through the custom panels, adding each panel. While we’re in a panel, we’re looping through it and adding all of the sections for that panel. When we’re in a section, we loop through the settings in that section. Whenever we add a setting, we also have to add a control for that setting. Loopy stuff!

You can see the guts of this more easily by browsing the source file.

This method of looping through an array is purely my personal technique. I like this because I can easily add a new setting by just adding it to my definition array. I don’t have to touch or create any other file. See the codex for documentation on the functions I’m using to interact with $wp_customize, such as add_setting().

Let’s Talk About Live Preview

The preview pane makes it wicked fast to select and view theme modifications on the fly:

The reason it updates so quickly is because “live preview” has been enabled. However, it’s not part of our example theme, nor do I personally use it in production. There are two common ways to do live preview, and I’m not happy with either one.

#1: Inline Styles on an Element

The most commonly documented way to do live preview has a tendency to crush the C in CSS. In other words, JS is used to create, inject, and update inline styles on the selected HTML elements. Something like this, which I’m mostly lifting from the codex:

<script>
  
  // Update default link color.
  wp.customize( 'mytheme_options[link_color]', function( value ) {
    value.bind( function( newval ) {
      $( 'a' ).css( 'color', newval );
    } );
  } );
  
  // Update sidebar link color.
  wp.customize( 'mytheme_options[sidebar_link_color]', function( value ) {
    value.bind( function( newval ) {
      $( '.sidebar a' ).css( 'color', newval );
    } );
  } );  
  
</script>

Do you see the problem there? By updating the link color for body links, I’d inadvertently demolish the more specific styles for sidebar links during the preview period. That’s a problem, and I don’t have a solution for it.

Another problem with this technique is media queries. Recall that we have scoped our theme mods to specific media queries. It would be a lot of work to pass that logic via JS.

#2: Inline Style Blocks

If you dig through Twenty Fifteen, you’ll see that instead of updating the styles directly on an element, they actually update the block of inline styles that the customizer generates. I think this is much, much nicer. You can get a foothold on this technique here. I tried to trace this back and re-code it into something I could really understand and maintain on my own, and had trouble. I found some action here, but that ticket actually doesn’t make me feel too bad about sitting out on live preview for a while. I’m keeping my eye on the Customizer API, but I don’t currently use it in production, and I’m not going to dig into it here.

That’s the thing about the customizer: If hundreds of client sites run the same theme, we have to be really careful when it comes time to update the theme. I’d probably need visual regression testing in order to be responsible. Because of that, I tend to stay farther away from the cutting-edge of WordPress functionality with the customizer than I would with a single-client theme. If core releases a breaking change, I don’t want to have to worry about hundreds of sites.

The bottom line is that live preview is a nice-to-have feature, but is not a deal-breaker. I’m personally opting out until the JS API has better documentation.

Styling the Front End

We did all that work to get a UI for our settings and it’s working! We can use these values anywhere we’d like: CSS at wp_head(), body classes, the TinyMCE editor—anything you can think of. We can even pass the values to our JS.

Let’s use them to output CSS. We already have some settings to control color and background-color on the body element, so we can start there.

The theme has a class called CSST_TMD_Inline_Styles which loops through all of our settings and outputs the appropriate CSS. This happens via the excellent add_inline_style() function, which means that custom styles get enqueued as a dependency of the theme stylesheet. Here’s what that looks like in action:

<?php

class CSST_TMD_Inline_Styles {

  public function __construct( $output_for = 'front_end' ) {

    // Add our styles to the front end of the blog.
    add_action( 'wp_enqueue_scripts', array( $this, 'front_end_styles' ) );
    
    ...

  }

  /**
   * Append our customizer styles to the <head> whenever our main stylesheet is called.
   */
  public function front_end_styles() {

    // Grab the styles that pertain to the front end, but don't wrap them in a style tag.
    $styles = $this -> get_inline_styles( 'unwrapped', 'front_end' );

    // Attach our customizer styles to our stylesheet. When it gets called, so do our customizer styles.
    wp_add_inline_style( CSST_TMD, $styles );

  }
  
  ...
  
?>

We’re calling a method, $this -> get_inline_styles(). That method is responsible for looping through every customizer setting and outputting a giant block of CSS:

<?php

class CSST_TMD_Inline_Styles {

  ...

  /**
   * Loop through our theme mods and build a string of CSS rules.
   * 
   * @param string $wrapped  Whether or not to wrap the styles in a style tag. Expects 'wrapped' or 'unwrapped'.
   * @param string $output_for The context for these styles. Expects 'front_end' or 'tinymce'.
   * @return string CSS, either wrapped in a style tag, or not.
   */
  public function get_inline_styles( $wrapped = 'wrapped', $output_for = 'front_end' ) {

    // This will hold all of our customizer styles.
    $out = '';

    ...

    // For each setting...
    foreach( $settings as $setting_id => $setting ) {

      // Grab the css for this setting.
      $css_rules = $setting['css'];

      // Grab the current value for this setting.
      $value = $setting['value'];

      // For each css rule...
      foreach( $css_rules as $css_rule ) {

        // The css selector.
        $selector = $css_rule['selector'];
        
        // The css property.
        $property = $css_rule['property'];

        // Build this into a CSS rule.
        $rule_string = "$selector { $property : $value ; }";

        // Does this css rule have media queries?
        if( isset( $css_rule['queries'] ) ) {

          ...
          
          foreach( $queries as $query_key => $query_value ) {

            ...

            // Add the media query key and value.
            $query .= "( $query_key : $query_value )";

            // If this isn't the last query, add the "and" operator.
            if( $i < $query_count ) {
              $query .= ' and ';
            }
            
            ...

          }

          // Wrap the rule string in the media query.
          $rule_string = " @media $query { $rule_string } ";

        }

        // Add the rule, which might be wrapped in a media query, to the output.
        $out .= $rule_string;

      }

    } 
  
    ...
    
    return $out;

  }

}

?>

A block of inline styles is being built, one line at a time, where each CSS selector and CSS property is defined in the settings array, and the CSS value is what gets saved via the customizer UI.

Other Uses for Customizer Settings

The above example is probably the most important and obvious use for the customizer, but it’s far from the only one. We also have a class, CSST_TMD_Body_Classes, where we’re adding each setting as a body class, both in wp-admin and the front end. This isn’t very important for settings like color, but it could be helpful if we had a setting for, say, floating a sidebar left or right. Similarly, we have another class, CSST_TMD_Tiny_MCE to style the post editor in wp-admin, so that the editor view carries the same color scheme that the user chose in the customizer. I’ll leave it to the curious reader to dig into those; they don’t cover new ground related to the customizer, per se.

Resources and Next Steps

If you found this article helpful, I’d highly encourage you to follow the discussions for the customizer on the Make blog. A recent article there noted front-end performance as an area of concern with the customizer. I would agree with that, though I find performance to be much better on Chrome than on Firefox. They also mentioned the possibility of revisions for the customizer, similar to post revisions, which would be a welcome feature in an agency environment like mine.

I’ve seen a few collections of custom controls floating around GitHub over the years. This one looks like it has some useful and complex controls. Here’s another. Do Let me know in the comments if you have any I should be following.

If you felt like you were able to follow the content of this article, some logical next steps include:

  • Add more settings to the settings array.
  • Make the media query syntax more robust, so that you can register media queries with specific Logical Operators.
  • Get smart on JavaScript templating for custom control types, via Justin Tadlock.

If you found this article difficult to follow, I’ll be happy to respond via the comments. You could also go back in time to, say, Twenty Fourteen and dissect the customizer code from a simpler era, working your way forward through each annual theme. When in doubt, var_dump()!