Introduction to the WordPress Customizer
The WordPress Customizer is a powerful tool that allows users to modify and preview theme settings in real-time before publishing them to the live site. It provides a user-friendly interface for customizing various aspects of a WordPress theme, from colors and fonts to layouts and content.
Think of the Customizer as a control panel for your theme – it's like having access to the backstage controls of a theater, where you can adjust the lighting, scenery, and sound while immediately seeing how those changes affect the performance on stage.
For theme developers, the Customizer API provides a standardized way to add customization options to themes. It helps create a consistent experience for users while giving them the power to personalize their websites without writing code.
Why Use the Customizer?
For Users
- Real-time preview of changes
- User-friendly interface
- Ability to experiment without affecting the live site
- No coding knowledge required
- Consistent experience across different themes
For Developers
- Standardized API for creating theme options
- Improved user experience compared to custom options pages
- Separation of settings and controls
- Ability to create complex customization options
- Live JavaScript preview capabilities
- Better theme adoption through customizability
Customizer Components
The WordPress Customizer is built on several key components:
Panels
High-level containers that group related sections together. For example, a "Typography" panel might contain sections for headings, body text, and buttons.
Sections
Groups of related controls. For example, a "Colors" section might contain controls for the background color, text color, and link color.
Settings
Data stored in the database. Each setting corresponds to a specific theme option, like "header_background_color" or "site_title_font_size".
Controls
UI elements that allow users to manipulate settings. Controls can be color pickers, text inputs, dropdowns, radio buttons, etc.
Transport Methods
Determine how changes are previewed. The "refresh" method reloads the preview, while "postMessage" updates the preview using JavaScript without reloading.
Understanding these components and how they interact is crucial for effectively implementing the Customizer in your themes.
Setting Up the Customizer
Let's start by setting up the basic structure for adding Customizer functionality to a WordPress theme. In most themes, Customizer code is placed in a separate file that is included in the theme's functions.php file.
File Structure
theme-folder/
├── functions.php
├── inc/
│ └── customizer.php
└── ... (other theme files)
functions.php
<?php
/**
* Theme functions and definitions
*/
// ... other theme functions ...
/**
* Customizer additions.
*/
require get_template_directory() . '/inc/customizer.php';
inc/customizer.php
<?php
/**
* Theme Customizer functionality
*/
/**
* Add postMessage support and register Customizer settings, sections, and controls.
*
* @param WP_Customize_Manager $wp_customize Customizer manager instance.
*/
function mytheme_customize_register( $wp_customize ) {
// Add settings, sections, and controls here
}
add_action( 'customize_register', 'mytheme_customize_register' );
/**
* Enqueue scripts for customizer preview.
*/
function mytheme_customize_preview_js() {
wp_enqueue_script(
'mytheme-customizer',
get_template_directory_uri() . '/assets/js/customizer.js',
array( 'customize-preview' ),
'20230615',
true
);
}
add_action( 'customize_preview_init', 'mytheme_customize_preview_js' );
This basic setup establishes the foundation for adding Customizer functionality to your theme. The mytheme_customize_register function is the main function where you'll define your Customizer settings, sections, and controls. The mytheme_customize_preview_js function enqueues a JavaScript file for live preview functionality.
Now, let's look at how to add actual customization options to your theme.
Adding Basic Customization Options
Let's start by adding some basic customization options for colors and text. We'll create sections for these options and add controls for users to modify them.
inc/customizer.php (with basic options)
<?php
/**
* Theme Customizer functionality
*/
/**
* Add postMessage support and register Customizer settings, sections, and controls.
*
* @param WP_Customize_Manager $wp_customize Customizer manager instance.
*/
function mytheme_customize_register( $wp_customize ) {
/*
* =============================
* 1. Site Identity Modifications
* =============================
*/
// Add setting for site title color
$wp_customize->add_setting(
'site_title_color',
array(
'default' => '#000000',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
// Add control for site title color
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'site_title_color',
array(
'label' => __( 'Site Title Color', 'mytheme' ),
'section' => 'title_tagline', // This is an existing section
'settings' => 'site_title_color',
'priority' => 10,
)
)
);
/*
* =============================
* 2. Theme Colors Section
* =============================
*/
// Add a new section for theme colors
$wp_customize->add_section(
'mytheme_colors',
array(
'title' => __( 'Theme Colors', 'mytheme' ),
'description' => __( 'Customize the colors of your theme', 'mytheme' ),
'priority' => 40,
)
);
// Primary Color Setting
$wp_customize->add_setting(
'primary_color',
array(
'default' => '#0073aa',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
// Primary Color Control
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'primary_color',
array(
'label' => __( 'Primary Color', 'mytheme' ),
'section' => 'mytheme_colors',
'settings' => 'primary_color',
)
)
);
// Background Color Setting
$wp_customize->add_setting(
'background_color',
array(
'default' => '#ffffff',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
// Background Color Control
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'background_color',
array(
'label' => __( 'Background Color', 'mytheme' ),
'section' => 'mytheme_colors',
'settings' => 'background_color',
)
)
);
// Text Color Setting
$wp_customize->add_setting(
'text_color',
array(
'default' => '#333333',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
// Text Color Control
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'text_color',
array(
'label' => __( 'Text Color', 'mytheme' ),
'section' => 'mytheme_colors',
'settings' => 'text_color',
)
)
);
// Link Color Setting
$wp_customize->add_setting(
'link_color',
array(
'default' => '#0073aa',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
// Link Color Control
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'link_color',
array(
'label' => __( 'Link Color', 'mytheme' ),
'section' => 'mytheme_colors',
'settings' => 'link_color',
)
)
);
/*
* =============================
* 3. Typography Settings
* =============================
*/
// Add a new section for typography
$wp_customize->add_section(
'mytheme_typography',
array(
'title' => __( 'Typography', 'mytheme' ),
'description' => __( 'Customize the fonts and typography', 'mytheme' ),
'priority' => 45,
)
);
// Font Family Setting
$wp_customize->add_setting(
'body_font_family',
array(
'default' => 'Arial, sans-serif',
'sanitize_callback' => 'sanitize_text_field',
'transport' => 'postMessage',
)
);
// Font Family Control
$wp_customize->add_control(
'body_font_family',
array(
'type' => 'select',
'label' => __( 'Body Font Family', 'mytheme' ),
'description' => __( 'Select the main font for your site', 'mytheme' ),
'section' => 'mytheme_typography',
'choices' => array(
'Arial, sans-serif' => __( 'Arial', 'mytheme' ),
'Helvetica, sans-serif' => __( 'Helvetica', 'mytheme' ),
'Georgia, serif' => __( 'Georgia', 'mytheme' ),
'Tahoma, sans-serif' => __( 'Tahoma', 'mytheme' ),
'Verdana, sans-serif' => __( 'Verdana', 'mytheme' ),
'Times New Roman, serif' => __( 'Times New Roman', 'mytheme' ),
),
)
);
// Font Size Setting
$wp_customize->add_setting(
'body_font_size',
array(
'default' => '16',
'sanitize_callback' => 'absint', // Ensures we have a positive integer
'transport' => 'postMessage',
)
);
// Font Size Control
$wp_customize->add_control(
'body_font_size',
array(
'type' => 'number',
'label' => __( 'Body Font Size (px)', 'mytheme' ),
'section' => 'mytheme_typography',
'input_attrs' => array(
'min' => 12,
'max' => 24,
'step' => 1,
),
)
);
// Line Height Setting
$wp_customize->add_setting(
'body_line_height',
array(
'default' => '1.6',
'sanitize_callback' => 'mytheme_sanitize_float',
'transport' => 'postMessage',
)
);
// Line Height Control
$wp_customize->add_control(
'body_line_height',
array(
'type' => 'number',
'label' => __( 'Body Line Height', 'mytheme' ),
'section' => 'mytheme_typography',
'input_attrs' => array(
'min' => 1,
'max' => 2,
'step' => 0.1,
),
)
);
}
add_action( 'customize_register', 'mytheme_customize_register' );
/**
* Sanitize float value.
*
* @param float $input Float value to sanitize.
* @return float Sanitized float.
*/
function mytheme_sanitize_float( $input ) {
return filter_var( $input, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
}
/**
* Enqueue scripts for customizer preview.
*/
function mytheme_customize_preview_js() {
wp_enqueue_script(
'mytheme-customizer',
get_template_directory_uri() . '/assets/js/customizer.js',
array( 'customize-preview' ),
'20230615',
true
);
}
add_action( 'customize_preview_init', 'mytheme_customize_preview_js' );
This code adds several basic customization options:
- A site title color option in the existing "Site Identity" section
- A new "Theme Colors" section with options for primary, background, text, and link colors
- A new "Typography" section with options for font family, font size, and line height
Notice that each setting has a sanitization callback to ensure that the data is clean before being stored in the database. We also defined our own mytheme_sanitize_float function to handle the line height value.
The transport parameter is set to postMessage for all settings, which means that changes will be previewed using JavaScript without reloading the preview. This provides a better user experience, but we need to implement the JavaScript part as well.
Implementing Live Preview with JavaScript
To make our Customizer settings update in real-time without reloading the preview, we need to write some JavaScript. Let's create the customizer.js file that we enqueued earlier:
assets/js/customizer.js
/**
* Theme Customizer live preview
*/
( function( $ ) {
// Site title color
wp.customize( 'site_title_color', function( value ) {
value.bind( function( newVal ) {
$( '.site-title a' ).css( 'color', newVal );
} );
} );
// Primary color
wp.customize( 'primary_color', function( value ) {
value.bind( function( newVal ) {
// Apply to multiple elements that use the primary color
$( '.button, .primary-button, .main-navigation, .highlight' ).css( 'background-color', newVal );
} );
} );
// Background color
wp.customize( 'background_color', function( value ) {
value.bind( function( newVal ) {
$( 'body' ).css( 'background-color', newVal );
} );
} );
// Text color
wp.customize( 'text_color', function( value ) {
value.bind( function( newVal ) {
$( 'body' ).css( 'color', newVal );
} );
} );
// Link color
wp.customize( 'link_color', function( value ) {
value.bind( function( newVal ) {
$( 'a' ).css( 'color', newVal );
} );
} );
// Body font family
wp.customize( 'body_font_family', function( value ) {
value.bind( function( newVal ) {
$( 'body' ).css( 'font-family', newVal );
} );
} );
// Body font size
wp.customize( 'body_font_size', function( value ) {
value.bind( function( newVal ) {
$( 'body' ).css( 'font-size', newVal + 'px' );
} );
} );
// Body line height
wp.customize( 'body_line_height', function( value ) {
value.bind( function( newVal ) {
$( 'body' ).css( 'line-height', newVal );
} );
} );
} )( jQuery );
This JavaScript code uses the WordPress Customizer API to update the preview when settings are changed. For each setting, we bind a function that updates the appropriate CSS when the value changes.
The wp.customize() function is provided by WordPress and allows us to interact with the Customizer API. The first argument is the setting ID, and the callback receives a value object that we can bind to.
Now we need to output these customizations to the front end of the site when the user saves their changes.
Applying Customizer Settings to the Theme
There are two main ways to apply Customizer settings to your theme: inline CSS or dynamic stylesheets. Let's look at both approaches:
Method 1: Inline CSS in the Head
inc/customizer.php (adding output function)
/**
* Output customizer styles inline in the head.
*/
function mytheme_customizer_css() {
?>
<style type="text/css">
/* Site Title Color */
.site-title a {
color: <?php echo esc_attr( get_theme_mod( 'site_title_color', '#000000' ) ); ?>;
}
/* Primary Color */
.button, .primary-button, .main-navigation, .highlight {
background-color: <?php echo esc_attr( get_theme_mod( 'primary_color', '#0073aa' ) ); ?>;
}
/* Background Color */
body {
background-color: <?php echo esc_attr( get_theme_mod( 'background_color', '#ffffff' ) ); ?>;
}
/* Text Color */
body {
color: <?php echo esc_attr( get_theme_mod( 'text_color', '#333333' ) ); ?>;
}
/* Link Color */
a {
color: <?php echo esc_attr( get_theme_mod( 'link_color', '#0073aa' ) ); ?>;
}
/* Typography */
body {
font-family: <?php echo esc_attr( get_theme_mod( 'body_font_family', 'Arial, sans-serif' ) ); ?>;
font-size: <?php echo esc_attr( get_theme_mod( 'body_font_size', '16' ) ); ?>px;
line-height: <?php echo esc_attr( get_theme_mod( 'body_line_height', '1.6' ) ); ?>;
}
</style>
This method adds inline CSS to the head of the document. It's simple and works well for a few customizations, but can become unwieldy for larger themes.
Method 2: Dynamic Stylesheet
inc/customizer.php (generating dynamic CSS file)
/**
* Generate CSS from customizer settings.
*/
function mytheme_get_customizer_css() {
$css = '
/* Site Title Color */
.site-title a {
color: ' . esc_attr( get_theme_mod( 'site_title_color', '#000000' ) ) . ';
}
/* Primary Color */
.button, .primary-button, .main-navigation, .highlight {
background-color: ' . esc_attr( get_theme_mod( 'primary_color', '#0073aa' ) ) . ';
}
/* Background Color */
body {
background-color: ' . esc_attr( get_theme_mod( 'background_color', '#ffffff' ) ) . ';
}
/* Text Color */
body {
color: ' . esc_attr( get_theme_mod( 'text_color', '#333333' ) ) . ';
}
/* Link Color */
a {
color: ' . esc_attr( get_theme_mod( 'link_color', '#0073aa' ) ) . ';
}
/* Typography */
body {
font-family: ' . esc_attr( get_theme_mod( 'body_font_family', 'Arial, sans-serif' ) ) . ';
font-size: ' . esc_attr( get_theme_mod( 'body_font_size', '16' ) ) . 'px;
line-height: ' . esc_attr( get_theme_mod( 'body_line_height', '1.6' ) ) . ';
}
';
return $css;
}
/**
* Enqueue customizer CSS.
*/
function mytheme_enqueue_customizer_css() {
wp_enqueue_style( 'mytheme-customizer', get_template_directory_uri() . '/assets/css/customizer.css' );
wp_add_inline_style( 'mytheme-customizer', mytheme_get_customizer_css() );
}
add_action( 'wp_enqueue_scripts', 'mytheme_enqueue_customizer_css', 20 );
This method creates a base CSS file for customizations and then adds the dynamic styles as inline styles attached to that stylesheet. This approach is cleaner and more maintainable for larger themes.
For this to work, you need to create a basic customizer.css file in your theme's assets/css directory. It can be empty or contain default styles that the customizer will override.
A Note on Performance
For better performance, you might want to consider caching the customizer CSS. Here's a simple implementation:
Caching Customizer CSS
/**
* Generate and cache customizer CSS.
*/
function mytheme_get_cached_customizer_css() {
// Get the cached CSS if it exists
$cached_css = get_transient( 'mytheme_customizer_css' );
// If we have cached CSS and we're not in the customizer preview, return it
if ( $cached_css && ! is_customize_preview() ) {
return $cached_css;
}
// Generate fresh CSS
$css = mytheme_get_customizer_css();
// Cache the CSS for 24 hours (86400 seconds)
set_transient( 'mytheme_customizer_css', $css, 86400 );
return $css;
}
/**
* Delete cached CSS when customizer settings are saved.
*/
function mytheme_reset_customizer_cache() {
delete_transient( 'mytheme_customizer_css' );
}
add_action( 'customize_save_after', 'mytheme_reset_customizer_cache' );
/**
* Enqueue cached customizer CSS.
*/
function mytheme_enqueue_cached_customizer_css() {
wp_enqueue_style( 'mytheme-customizer', get_template_directory_uri() . '/assets/css/customizer.css' );
wp_add_inline_style( 'mytheme-customizer', mytheme_get_cached_customizer_css() );
}
add_action( 'wp_enqueue_scripts', 'mytheme_enqueue_cached_customizer_css', 20 );
This implementation uses WordPress transients to cache the CSS for 24 hours. When the customizer settings are saved, the cache is cleared so that the new settings are applied immediately.
Advanced Customizer Features
Now that we've covered the basics, let's explore some more advanced Customizer features that can enhance your theme's customization options.
Creating Panels for Better Organization
When your theme has many customization options, you can organize them into panels for better usability:
Adding a Panel
// Add a panel for all theme options
$wp_customize->add_panel(
'mytheme_options',
array(
'title' => __( 'Theme Options', 'mytheme' ),
'description' => __( 'All theme customization options', 'mytheme' ),
'priority' => 30,
)
);
// Add sections to the panel
$wp_customize->add_section(
'mytheme_colors',
array(
'title' => __( 'Theme Colors', 'mytheme' ),
'description' => __( 'Customize the colors of your theme', 'mytheme' ),
'panel' => 'mytheme_options', // This is the key part that adds the section to the panel
'priority' => 10,
)
);
$wp_customize->add_section(
'mytheme_typography',
array(
'title' => __( 'Typography', 'mytheme' ),
'description' => __( 'Customize the fonts and typography', 'mytheme' ),
'panel' => 'mytheme_options',
'priority' => 20,
)
);
Custom Control Types
WordPress includes several specialized control types, and you can also create your own:
Using Built-in Custom Controls
// Add an image upload control
$wp_customize->add_setting(
'header_image',
array(
'default' => '',
'sanitize_callback' => 'esc_url_raw',
)
);
$wp_customize->add_control(
new WP_Customize_Image_Control(
$wp_customize,
'header_image',
array(
'label' => __( 'Header Image', 'mytheme' ),
'section' => 'mytheme_header',
'settings' => 'header_image',
)
)
);
// Add a cropped image control
$wp_customize->add_setting(
'site_logo',
array(
'default' => '',
'sanitize_callback' => 'esc_url_raw',
)
);
$wp_customize->add_control(
new WP_Customize_Cropped_Image_Control(
$wp_customize,
'site_logo',
array(
'label' => __( 'Site Logo', 'mytheme' ),
'section' => 'title_tagline',
'settings' => 'site_logo',
'width' => 300,
'height' => 100,
'flex_width' => true,
'flex_height' => true,
)
)
);
// Add a media control
$wp_customize->add_setting(
'background_video',
array(
'default' => '',
'sanitize_callback' => 'absint',
)
);
$wp_customize->add_control(
new WP_Customize_Media_Control(
$wp_customize,
'background_video',
array(
'label' => __( 'Background Video', 'mytheme' ),
'section' => 'mytheme_header',
'settings' => 'background_video',
'mime_type' => 'video',
)
)
);
Creating a Custom Range Control
/**
* Custom Range Control class.
*/
class Mytheme_Customize_Range_Control extends WP_Customize_Control {
/**
* Control type.
*
* @var string
*/
public $type = 'range';
/**
* Minimum value.
*
* @var int
*/
public $min = 0;
/**
* Maximum value.
*
* @var int
*/
public $max = 100;
/**
* Step value.
*
* @var int
*/
public $step = 1;
/**
* Render control content.
*/
public function render_content() {
?>
<label>
<?php if ( ! empty( $this->label ) ) : ?>
<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
<?php endif; ?>
<?php if ( ! empty( $this->description ) ) : ?>
<span class="description customize-control-description"><?php echo esc_html( $this->description ); ?></span>
<?php endif; ?>
<div class="range-slider">
<input type="range" <?php $this->input_attrs(); ?> value="<?php echo esc_attr( $this->value() ); ?>" <?php $this->link(); ?> min="<?php echo esc_attr( $this->min ); ?>" max="<?php echo esc_attr( $this->max ); ?>" step="<?php echo esc_attr( $this->step ); ?>" />
<span class="range-value"><?php echo esc_attr( $this->value() ); ?></span>
</div>
add_setting(
'content_width',
array(
'default' => '1200',
'sanitize_callback' => 'absint',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
new Mytheme_Customize_Range_Control(
$wp_customize,
'content_width',
array(
'label' => __( 'Content Width (px)', 'mytheme' ),
'section' => 'mytheme_layout',
'settings' => 'content_width',
'min' => 800,
'max' => 1600,
'step' => 10,
)
)
);
Customizer Selective Refresh
Selective refresh is a more advanced version of postMessage transport that allows you to refresh only specific parts of the preview instead of using JavaScript to apply changes:
Adding Selective Refresh Support
// Add selective refresh support for the site title
$wp_customize->selective_refresh->add_partial(
'blogname',
array(
'selector' => '.site-title',
'render_callback' => function() {
return get_bloginfo( 'name' );
},
)
);
// Add selective refresh support for a custom setting
$wp_customize->add_setting(
'footer_text',
array(
'default' => 'Copyright © ' . date('Y') . ' ' . get_bloginfo( 'name' ),
'sanitize_callback' => 'wp_kses_post',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
'footer_text',
array(
'label' => __( 'Footer Text', 'mytheme' ),
'section' => 'mytheme_footer',
'type' => 'textarea',
)
);
$wp_customize->selective_refresh->add_partial(
'footer_text',
array(
'selector' => '.site-footer .footer-text',
'render_callback' => function() {
return get_theme_mod( 'footer_text' );
},
)
);
Selective refresh combines the performance benefits of postMessage with the accuracy of refresh. Instead of updating the CSS with JavaScript, it reloads only the specific part of the page that has changed.
Active Callbacks for Conditional Controls
You can show or hide controls based on the values of other controls using active callbacks:
Using Active Callbacks
// Add a setting for enabling a feature
$wp_customize->add_setting(
'enable_header_search',
array(
'default' => true,
'sanitize_callback' => 'mytheme_sanitize_checkbox',
)
);
$wp_customize->add_control(
'enable_header_search',
array(
'label' => __( 'Enable Header Search', 'mytheme' ),
'section' => 'mytheme_header',
'type' => 'checkbox',
)
);
// Add settings that are only relevant if header search is enabled
$wp_customize->add_setting(
'search_placeholder',
array(
'default' => __( 'Search...', 'mytheme' ),
'sanitize_callback' => 'sanitize_text_field',
)
);
$wp_customize->add_control(
'search_placeholder',
array(
'label' => __( 'Search Placeholder Text', 'mytheme' ),
'section' => 'mytheme_header',
'type' => 'text',
'active_callback' => 'mytheme_is_header_search_enabled',
)
);
// Callback function to check if header search is enabled
function mytheme_is_header_search_enabled() {
return get_theme_mod( 'enable_header_search', true );
}
// Sanitize checkbox function
function mytheme_sanitize_checkbox( $checked ) {
return ( ( isset( $checked ) && true == $checked ) ? true : false );
}
Active callbacks provide a clean way to create dynamic, context-aware customization interfaces. Controls appear or disappear based on the current state of other settings.
Real-World Examples
Let's look at some real-world examples of how the Customizer can be used to create practical, user-friendly theme options:
Example 1: Header Customization
// Add a section for header options
$wp_customize->add_section(
'mytheme_header',
array(
'title' => __( 'Header Options', 'mytheme' ),
'description' => __( 'Customize your site header', 'mytheme' ),
'panel' => 'mytheme_options',
'priority' => 30,
)
);
// Header Layout
$wp_customize->add_setting(
'header_layout',
array(
'default' => 'standard',
'sanitize_callback' => 'mytheme_sanitize_select',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
'header_layout',
array(
'label' => __( 'Header Layout', 'mytheme' ),
'section' => 'mytheme_header',
'type' => 'select',
'choices' => array(
'standard' => __( 'Standard (Logo + Menu)', 'mytheme' ),
'centered' => __( 'Centered (Logo above Menu)', 'mytheme' ),
'split' => __( 'Split (Menu - Logo - Menu)', 'mytheme' ),
'transparent' => __( 'Transparent Overlay', 'mytheme' ),
),
)
);
// Sticky Header
$wp_customize->add_setting(
'sticky_header',
array(
'default' => false,
'sanitize_callback' => 'mytheme_sanitize_checkbox',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
'sticky_header',
array(
'label' => __( 'Enable Sticky Header', 'mytheme' ),
'section' => 'mytheme_header',
'type' => 'checkbox',
)
);
// Header Background Color
$wp_customize->add_setting(
'header_background',
array(
'default' => '#ffffff',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'header_background',
array(
'label' => __( 'Header Background Color', 'mytheme' ),
'section' => 'mytheme_header',
'settings' => 'header_background',
)
)
);
// Header Text Color
$wp_customize->add_setting(
'header_text_color',
array(
'default' => '#000000',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'header_text_color',
array(
'label' => __( 'Header Text Color', 'mytheme' ),
'section' => 'mytheme_header',
'settings' => 'header_text_color',
)
)
);
// Header Padding
$wp_customize->add_setting(
'header_padding',
array(
'default' => '20',
'sanitize_callback' => 'absint',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
'header_padding',
array(
'label' => __( 'Header Padding (px)', 'mytheme' ),
'section' => 'mytheme_header',
'type' => 'number',
'input_attrs' => array(
'min' => 0,
'max' => 50,
'step' => 1,
),
)
);
// JavaScript for live preview
function mytheme_header_customizer_js() {
?>
This example provides comprehensive header customization options, including layout selection, sticky behavior, colors, and spacing. The JavaScript preview code updates the header in real-time as users make changes.
Example 2: Blog Layout Customization
// Add a section for blog options
$wp_customize->add_section(
'mytheme_blog',
array(
'title' => __( 'Blog Options', 'mytheme' ),
'description' => __( 'Customize your blog layout and features', 'mytheme' ),
'panel' => 'mytheme_options',
'priority' => 40,
)
);
// Blog Layout
$wp_customize->add_setting(
'blog_layout',
array(
'default' => 'standard',
'sanitize_callback' => 'mytheme_sanitize_select',
)
);
$wp_customize->add_control(
'blog_layout',
array(
'label' => __( 'Blog Layout', 'mytheme' ),
'section' => 'mytheme_blog',
'type' => 'select',
'choices' => array(
'standard' => __( 'Standard (Full Width)', 'mytheme' ),
'grid' => __( 'Grid (2 Columns)', 'mytheme' ),
'grid-3' => __( 'Grid (3 Columns)', 'mytheme' ),
'masonry' => __( 'Masonry', 'mytheme' ),
),
)
);
// Post Meta Display
$wp_customize->add_setting(
'show_post_date',
array(
'default' => true,
'sanitize_callback' => 'mytheme_sanitize_checkbox',
)
);
$wp_customize->add_control(
'show_post_date',
array(
'label' => __( 'Show Post Date', 'mytheme' ),
'section' => 'mytheme_blog',
'type' => 'checkbox',
)
);
$wp_customize->add_setting(
'show_post_author',
array(
'default' => true,
'sanitize_callback' => 'mytheme_sanitize_checkbox',
)
);
$wp_customize->add_control(
'show_post_author',
array(
'label' => __( 'Show Post Author', 'mytheme' ),
'section' => 'mytheme_blog',
'type' => 'checkbox',
)
);
$wp_customize->add_setting(
'show_post_categories',
array(
'default' => true,
'sanitize_callback' => 'mytheme_sanitize_checkbox',
)
);
$wp_customize->add_control(
'show_post_categories',
array(
'label' => __( 'Show Categories', 'mytheme' ),
'section' => 'mytheme_blog',
'type' => 'checkbox',
)
);
$wp_customize->add_setting(
'show_post_tags',
array(
'default' => true,
'sanitize_callback' => 'mytheme_sanitize_checkbox',
)
);
$wp_customize->add_control(
'show_post_tags',
array(
'label' => __( 'Show Tags', 'mytheme' ),
'section' => 'mytheme_blog',
'type' => 'checkbox',
)
);
// Featured Image Options
$wp_customize->add_setting(
'featured_image_style',
array(
'default' => 'large',
'sanitize_callback' => 'mytheme_sanitize_select',
)
);
$wp_customize->add_control(
'featured_image_style',
array(
'label' => __( 'Featured Image Style', 'mytheme' ),
'section' => 'mytheme_blog',
'type' => 'select',
'choices' => array(
'large' => __( 'Large (Above Content)', 'mytheme' ),
'medium' => __( 'Medium (Aligned Right)', 'mytheme' ),
'thumbnail' => __( 'Thumbnail (Aligned Left)', 'mytheme' ),
'none' => __( 'No Featured Image', 'mytheme' ),
),
)
);
// Excerpt Length
$wp_customize->add_setting(
'excerpt_length',
array(
'default' => '55',
'sanitize_callback' => 'absint',
)
);
$wp_customize->add_control(
'excerpt_length',
array(
'label' => __( 'Excerpt Length (words)', 'mytheme' ),
'section' => 'mytheme_blog',
'type' => 'number',
'input_attrs' => array(
'min' => 10,
'max' => 200,
'step' => 5,
),
)
);
// Read More Text
$wp_customize->add_setting(
'read_more_text',
array(
'default' => __( 'Read More', 'mytheme' ),
'sanitize_callback' => 'sanitize_text_field',
)
);
$wp_customize->add_control(
'read_more_text',
array(
'label' => __( 'Read More Text', 'mytheme' ),
'section' => 'mytheme_blog',
'type' => 'text',
)
);
// Implement excerpt length filter
function mytheme_custom_excerpt_length( $length ) {
return get_theme_mod( 'excerpt_length', 55 );
}
add_filter( 'excerpt_length', 'mytheme_custom_excerpt_length' );
// Implement read more text filter
function mytheme_custom_excerpt_more( $more ) {
$read_more_text = get_theme_mod( 'read_more_text', __( 'Read More', 'mytheme' ) );
return '... ' . esc_html( $read_more_text ) . '';
}
add_filter( 'excerpt_more', 'mytheme_custom_excerpt_more' );
This example provides blog customization options, including layout selection, meta display preferences, featured image styles, and excerpt settings. It uses WordPress filters to implement some of these customizations.
Example 3: Footer Customization
// Add a section for footer options
$wp_customize->add_section(
'mytheme_footer',
array(
'title' => __( 'Footer Options', 'mytheme' ),
'description' => __( 'Customize your site footer', 'mytheme' ),
'panel' => 'mytheme_options',
'priority' => 50,
)
);
// Footer Widget Columns
$wp_customize->add_setting(
'footer_widget_columns',
array(
'default' => '3',
'sanitize_callback' => 'mytheme_sanitize_select',
)
);
$wp_customize->add_control(
'footer_widget_columns',
array(
'label' => __( 'Footer Widget Columns', 'mytheme' ),
'section' => 'mytheme_footer',
'type' => 'select',
'choices' => array(
'0' => __( 'No Footer Widgets', 'mytheme' ),
'1' => __( '1 Column', 'mytheme' ),
'2' => __( '2 Columns', 'mytheme' ),
'3' => __( '3 Columns', 'mytheme' ),
'4' => __( '4 Columns', 'mytheme' ),
),
)
);
// Footer Background Color
$wp_customize->add_setting(
'footer_background',
array(
'default' => '#222222',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'footer_background',
array(
'label' => __( 'Footer Background Color', 'mytheme' ),
'section' => 'mytheme_footer',
'settings' => 'footer_background',
)
)
);
// Footer Text Color
$wp_customize->add_setting(
'footer_text_color',
array(
'default' => '#ffffff',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'footer_text_color',
array(
'label' => __( 'Footer Text Color', 'mytheme' ),
'section' => 'mytheme_footer',
'settings' => 'footer_text_color',
)
)
);
// Footer Link Color
$wp_customize->add_setting(
'footer_link_color',
array(
'default' => '#cccccc',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
new WP_Customize_Color_Control(
$wp_customize,
'footer_link_color',
array(
'label' => __( 'Footer Link Color', 'mytheme' ),
'section' => 'mytheme_footer',
'settings' => 'footer_link_color',
)
)
);
// Footer Copyright Text
$wp_customize->add_setting(
'footer_copyright',
array(
'default' => '© ' . date('Y') . ' ' . get_bloginfo('name') . '. All rights reserved.',
'sanitize_callback' => 'wp_kses_post',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
'footer_copyright',
array(
'label' => __( 'Footer Copyright Text', 'mytheme' ),
'section' => 'mytheme_footer',
'type' => 'textarea',
)
);
// Footer Credit Visibility
$wp_customize->add_setting(
'footer_credit',
array(
'default' => true,
'sanitize_callback' => 'mytheme_sanitize_checkbox',
)
);
$wp_customize->add_control(
'footer_credit',
array(
'label' => __( 'Show "Powered by WordPress" Credit', 'mytheme' ),
'section' => 'mytheme_footer',
'type' => 'checkbox',
)
);
// JavaScript for live preview
function mytheme_footer_customizer_js() {
?>
sprintf( __( 'Footer Widget Area %d', 'mytheme' ), $i ),
'id' => 'footer-' . $i,
'description' => sprintf( __( 'Add widgets here to appear in footer column %d.', 'mytheme' ), $i ),
'before_widget' => '',
'before_title' => '',
'after_title' => '
',
) );
}
}
add_action( 'widgets_init', 'mytheme_footer_widgets_init' );
This example provides footer customization options, including widget columns, colors, copyright text, and credit visibility. It dynamically registers footer widget areas based on the selected number of columns.
Best Practices and Tips
As you develop customizer options for your themes, keep these best practices in mind:
Organization
- Group related options: Use panels and sections to organize options logically
- Use meaningful IDs: Choose setting and control IDs that clearly indicate their purpose
- Set appropriate priorities: Arrange sections and controls in a logical order using the priority parameter
- Include descriptive labels: Make sure each option has a clear, understandable label
- Add helpful descriptions: Include descriptions for complex options to guide users
Performance
- Use postMessage whenever possible: Avoid refreshing the entire preview for simple changes
- Minimize JavaScript: Keep your customizer JavaScript lean and efficient
- Cache customizer CSS: Use transients to cache generated CSS for better performance
- Avoid redundant queries: Don't retrieve theme mods inside loops
- Use selective refresh: For complex changes that can't be handled with simple CSS
Security
- Always sanitize inputs: Use appropriate sanitization callbacks for all settings
- Validate data thoroughly: Ensure that user inputs meet expected formats and ranges
- Escape output: Always escape theme mod values when outputting them
- Use capability checks: Ensure users have appropriate permissions to modify settings
- Prevent XSS: Be especially careful with settings that output HTML
User Experience
- Provide sensible defaults: Set reasonable default values for all options
- Use appropriate control types: Choose the right control type for each setting
- Implement conditional logic: Show/hide options based on context using active callbacks
- Add inline help: Use descriptions and tooltips to explain complex options
- Keep it simple: Don't overwhelm users with too many options
- Preview changes instantly: Use live preview for immediate feedback
Code Quality
- Follow WordPress coding standards: Maintain consistent style and formatting
- Use meaningful prefixes: Prefix all functions, settings, and controls with your theme's name
- Document your code: Add comments to explain complex sections
- Modularize your code: Break large customizer files into separate, focused files
- Test thoroughly: Check your customizer options across different browsers and devices
Common Pitfalls to Avoid
When implementing the Customizer, watch out for these common mistakes:
- Adding too many options: This can overwhelm users and make the customizer slow to load
- Forgetting sanitization: Always sanitize user input to prevent security issues
- Using refresh when postMessage would work: This creates a poor user experience
- Hardcoding CSS instead of using theme mods: Makes your theme less flexible
- Placing options in inappropriate sections: Organize options logically for better usability
- Creating redundant options: Don't duplicate existing WordPress functionality
- Using non-standard controls without documentation: Custom controls should be intuitive
- Ignoring mobile users: Test your Customizer interface on mobile devices
- Not respecting WordPress conventions: Follow established patterns for consistency
Testing Your Customizer Implementation
Before releasing your theme, thoroughly test your customizer implementation:
- Test all settings: Verify that each setting works as expected and applies correctly
- Check live preview: Ensure that the live preview updates properly for each setting
- Test edge cases: Try extreme values and unexpected inputs
- Verify saved settings: Make sure settings persist after saving and page reload
- Check browser compatibility: Test in different browsers and devices
- Validate performance: Ensure the customizer loads quickly and operates smoothly
- Check accessibility: Verify that your customizer is accessible to all users
- Test with different content: Try your customizations with various content scenarios
Customizer API Reference
For quick reference, here's a summary of the main Customizer API methods and parameters:
Adding Panels, Sections, Settings, and Controls
// Add a panel
$wp_customize->add_panel( $id, $args );
// Add a section
$wp_customize->add_section( $id, $args );
// Add a setting
$wp_customize->add_setting( $id, $args );
// Add a control
$wp_customize->add_control( $id, $args );
// Add a custom control
$wp_customize->add_control( new WP_Customize_Control_Type( $wp_customize, $id, $args ) );
Panel Parameters
$wp_customize->add_panel( 'example_panel', array(
'title' => __( 'Example Panel', 'mytheme' ), // Required
'description' => __( 'Panel description', 'mytheme' ), // Optional
'priority' => 10, // Optional
'capability' => 'edit_theme_options', // Optional
'theme_supports' => '', // Optional
'active_callback' => 'callback_function', // Optional
) );
Section Parameters
$wp_customize->add_section( 'example_section', array(
'title' => __( 'Example Section', 'mytheme' ), // Required
'description' => __( 'Section description', 'mytheme' ), // Optional
'panel' => 'example_panel', // Optional
'priority' => 10, // Optional
'capability' => 'edit_theme_options', // Optional
'theme_supports' => '', // Optional
'active_callback' => 'callback_function', // Optional
) );
Setting Parameters
$wp_customize->add_setting( 'example_setting', array(
'default' => 'default_value', // Optional
'transport' => 'refresh', // Optional (refresh/postMessage)
'capability' => 'edit_theme_options', // Optional
'theme_supports' => '', // Optional
'sanitize_callback' => 'sanitize_function', // Required for security
'sanitize_js_callback' => 'sanitize_js_function' // Optional
) );
Control Parameters
$wp_customize->add_control( 'example_control', array(
'label' => __( 'Example Control', 'mytheme' ), // Optional
'description' => __( 'Control description', 'mytheme' ), // Optional
'section' => 'example_section', // Required
'settings' => 'example_setting', // Required
'type' => 'text', // Optional
'priority' => 10, // Optional
'active_callback' => 'callback_function', // Optional
'input_attrs' => array( // Optional
'min' => 0,
'max' => 100,
'step' => 1,
),
'choices' => array( // Required for select, radio, checkbox
'option1' => 'Option 1',
'option2' => 'Option 2',
)
) );
Built-in Control Types
text- Text input fieldcheckbox- Checkbox fieldtextarea- Multi-line text arearadio- Radio buttonsselect- Dropdown select fielddropdown-pages- Dropdown of WordPress pagesemail- Email input fieldurl- URL input fieldnumber- Numeric input fieldhidden- Hidden input field
Special Control Classes
WP_Customize_Color_Control- Color pickerWP_Customize_Upload_Control- File upload fieldWP_Customize_Image_Control- Image selectorWP_Customize_Media_Control- Media selectorWP_Customize_Cropped_Image_Control- Image cropperWP_Customize_Site_Icon_Control- Site icon selectorWP_Customize_Header_Image_Control- Header image controlWP_Customize_Background_Image_Control- Background image controlWP_Customize_Date_Time_Control- Date and time selector
Common Sanitization Functions
// Text
'sanitize_text_field' // Sanitizes text by stripping tags and escaping
// Textarea
'sanitize_textarea_field' // Similar to sanitize_text_field but preserves line breaks
// URL
'esc_url_raw' // Sanitizes a URL without displaying it
// Email
'sanitize_email' // Sanitizes an email address
// Number
'absint' // Converts to absolute integer
'intval' // Converts to integer
'floatval' // Converts to float
// Colors
'sanitize_hex_color' // Sanitizes a hex color value
// Select/Radio
function mytheme_sanitize_select( $input, $setting ) {
// Get the list of choices from the control
$choices = $setting->manager->get_control( $setting->id )->choices;
// Return input if valid or return default if not
return ( array_key_exists( $input, $choices ) ? $input : $setting->default );
}
// Checkbox
function mytheme_sanitize_checkbox( $checked ) {
return ( ( isset( $checked ) && true == $checked ) ? true : false );
}
// HTML Content
function mytheme_sanitize_html( $html ) {
return wp_kses_post( $html );
}
Using Theme Mods in Templates
// Basic usage
$value = get_theme_mod( 'setting_id', 'default_value' );
// Example with escaping
echo esc_html( get_theme_mod( 'site_title', 'Default Title' ) );
// Example with image URL
$image_url = get_theme_mod( 'header_image', '' );
if ( $image_url ) {
echo '<img src="' . esc_url( $image_url ) . '" alt="">';
}
// Example with checkbox
if ( get_theme_mod( 'show_author', true ) ) {
echo '<span class="author">' . esc_html( get_the_author() ) . '</span>';
}
// Example with class conditional
$layout = get_theme_mod( 'layout_style', 'standard' );
echo '<div class="content layout-' . esc_attr( $layout ) . '">';
// Example with inline style
$color = get_theme_mod( 'primary_color', '#0073aa' );
echo '<a href="#" style="color: ' . esc_attr( $color ) . '">Link</a>';
Practical Exercises
Exercise 1: Basic Customizer Integration
Implement basic customizer options for a WordPress theme:
- Set up the customizer.php file and include it in functions.php
- Create a "Theme Options" section with color options for:
- Primary color
- Secondary color
- Text color
- Background color
- Add JavaScript for live preview of all options
- Output the customizations to the front end using inline CSS
- Test your implementation in different browsers
Exercise 2: Advanced Customizer Features
Enhance a theme with more advanced customizer features:
- Create a "Typography" panel with sections for:
- Headings (font family, size, weight, color)
- Body Text (font family, size, line height, color)
- Links (color, hover color, decoration)
- Implement a custom range control for font sizes
- Use active callbacks to show/hide certain controls based on selections
- Add selective refresh for complex settings
- Implement proper caching for the generated CSS
Exercise 3: Layout Customization
Add layout customization options to a theme:
- Create a "Layout" section with options for:
- Content width (with a range slider)
- Sidebar position (left, right, none)
- Header layout (standard, centered, transparent)
- Footer columns (1-4)
- Add a custom radio image control for visual layout selection
- Implement responsive considerations (mobile vs. desktop layouts)
- Use JavaScript to update the preview with layout changes
- Ensure the layout options work with different content types
Exercise 4: Create a Complete Theme Customizer
Build a comprehensive customizer implementation for a full theme:
- Create panels for:
- General Options (logo, site identity, favicon)
- Header Options (layout, colors, menu position)
- Footer Options (widgets, copyright, social icons)
- Typography (headings, body, special elements)
- Colors & Styles (color scheme, buttons, forms)
- Blog Options (layout, meta display, featured images)
- Implement at least one custom control type
- Use modular code organization with separate files
- Create a clean, performance-optimized implementation
- Test thoroughly across different browsers and devices
Summary
- The WordPress Customizer provides a user-friendly way to customize themes with live preview
- Customizer components include panels, sections, settings, and controls
- Settings store the actual data, while controls provide the UI for changing settings
- Transport methods determine how changes are previewed: refresh reloads the preview, while postMessage uses JavaScript
- You can create custom controls to provide specialized interfaces for specific settings
- Selective refresh allows updating specific parts of the preview without full reloads or custom JavaScript
- Active callbacks enable conditional logic for showing/hiding controls based on context
- Proper sanitization and validation are essential for security
- Organizing options into logical panels and sections improves usability
- Performance considerations include caching CSS and minimizing unnecessary preview refreshes
The WordPress Customizer API provides a powerful and flexible system for creating theme customization options. By following best practices and leveraging the full capabilities of the API, you can create a seamless customization experience for your theme users while maintaining good performance and security.