WordPress Theme Customizer
Learning Objectives
- Navigate and use the WordPress Customizer interface
- Understand live preview functionality
- Master built-in customizer controls
- Add custom sections, settings, and controls
- Implement selective refresh for better performance
- Create custom control types
- Handle data sanitization and validation
- Export and import customizer settings
Introduction to the Theme Customizer
The WordPress Theme Customizer provides a user-friendly interface for making design changes to your WordPress site with live preview. It allows users to customize various aspects of their theme without touching any code, making WordPress more accessible to non-developers.
Key Feature
Core Customizer Features
Live Preview
Responsive Preview
Draft & Schedule
Selective Refresh
Contextual Controls
Permission Based
The Customizer Interface
Live Preview Area
Your website preview appears here with real-time changes
Built-in Customizer Controls
Text Control
Single line text input for titles, labels, etc.
Textarea Control
Multi-line text input for descriptions
Color Picker
Visual color selection tool
Image/Media Control
Upload or select media from library
Select Dropdown
Choose from predefined options
Checkbox Control
Boolean true/false options
Radio Buttons
Select one from multiple options
Range Slider
Select numeric value within range
Adding Custom Customizer Options
// Hook into customize_register actionadd_action( 'customize_register', 'mytheme_customize_register' );
function mytheme_customize_register( $wp_customize ) {// Add a new section$wp_customize->add_section( 'mytheme_options', array(
'title' => __( 'Theme Options', 'mytheme' ),
'priority' => 30,
'description' => 'Customize your theme settings',
) );// Add a setting$wp_customize->add_setting( 'header_background_color', array(
'default' => '#ffffff',
'transport' => 'refresh',// or 'postMessage' for live preview'sanitize_callback' => 'sanitize_hex_color',
) );// Add a control$wp_customize->add_control( new WP_Customize_Color_Control(
$wp_customize,
'header_background_color',
array(
'label' => __( 'Header Background Color', 'mytheme' ),
'section' => 'mytheme_options',
'settings' => 'header_background_color',
)
) );// Add text setting$wp_customize->add_setting( 'footer_text', array(
'default' => 'Copyright © 2024',
'transport' => 'postMessage',
'sanitize_callback' => 'sanitize_text_field',
) );
$wp_customize->add_control( 'footer_text', array(
'label' => __( 'Footer Text', 'mytheme' ),
'section' => 'mytheme_options',
'type' => 'text',
) );// Add image upload$wp_customize->add_setting( 'hero_image', array(
'default' => '',
'transport' => 'refresh',
'sanitize_callback' => 'esc_url_raw',
) );
$wp_customize->add_control( new WP_Customize_Image_Control(
$wp_customize,
'hero_image',
array(
'label' => __( 'Hero Image', 'mytheme' ),
'section' => 'mytheme_options',
'settings' => 'hero_image',
)
) );
}
Transport Methods
| Transport Type | Description | Performance | Use Case |
|---|---|---|---|
| refresh | Full page reload on change | Slower | Complex changes, PHP processing needed |
| postMessage | JavaScript live update | Instant | Simple CSS changes, text updates |
Implementing postMessage Transport
// Set transport to postMessage
$wp_customize->add_setting( 'site_title', array(
'default' => get_bloginfo( 'name' ),
'transport' => 'postMessage',
) );
// Enqueue customizer JavaScript
add_action( 'customize_preview_init', function() {
wp_enqueue_script(
'mytheme-customizer',
get_template_directory_uri() . '/js/customizer.js',
array( 'customize-preview' ),
'1.0.0',
true
);
} );
// Live preview JavaScript
( function( $ ) {
// Site title
wp.customize( 'blogname', function( value ) {
value.bind( function( newval ) {
$( '.site-title' ).text( newval );
} );
} );
// Site description
wp.customize( 'blogdescription', function( value ) {
value.bind( function( newval ) {
$( '.site-description' ).text( newval );
} );
} );
// Header background color
wp.customize( 'header_background_color', function( value ) {
value.bind( function( newval ) {
$( '.site-header' ).css( 'background-color', newval );
} );
} );
// Footer text
wp.customize( 'footer_text', function( value ) {
value.bind( function( newval ) {
$( '.footer-copyright' ).html( newval );
} );
} );
} )( jQuery );
🔄 Selective Refresh
Selective refresh combines the benefits of both transport methods by only refreshing specific parts of the preview:
// Add selective refresh support
$wp_customize->selective_refresh->add_partial( 'blogname', array(
'selector' => '.site-title',
'container_inclusive' => false,
'render_callback' => function() {
return get_bloginfo( 'name' );
},
) );
// For widgets
add_theme_support( 'customize-selective-refresh-widgets' );
// Custom partial
$wp_customize->selective_refresh->add_partial( 'footer_copyright', array(
'selector' => '.footer-copyright',
'settings' => array( 'footer_text' ),
'render_callback' => function() {
return get_theme_mod( 'footer_text' );
},
'fallback_refresh' => true, // Fallback to full refresh if needed
) );
Creating Custom Control Types
// Custom control class
class WP_Customize_Range_Control extends WP_Customize_Control {
public $type = 'range';
public function render_content() {
?>
<label>
<span class="customize-control-title">
<?php echo esc_html( $this->label ); ?>
</span>
<?php if ( $this->description ) : ?>
<span class="description">
<?php echo esc_html( $this->description ); ?>
</span>
<?php endif; ?>
<input type="range"
<?php $this->link(); ?>
value="<?php echo esc_attr( $this->value() ); ?>"
min="<?php echo esc_attr( $this->input_attrs['min'] ); ?>"
max="<?php echo esc_attr( $this->input_attrs['max'] ); ?>"
step="<?php echo esc_attr( $this->input_attrs['step'] ); ?>"
/>
<span class="range-value">
<?php echo esc_html( $this->value() ); ?>
</span>
</label>
<?php
}
}
// Register the custom control
$wp_customize->add_control( new WP_Customize_Range_Control(
$wp_customize,
'font_size',
array(
'label' => __( 'Font Size', 'mytheme' ),
'section' => 'typography',
'settings' => 'font_size',
'input_attrs' => array(
'min' => 12,
'max' => 48,
'step' => 1,
),
)
) );
Toggle Switch Control
class WP_Customize_Toggle_Control extends WP_Customize_Control {
public $type = 'toggle';
public function render_content() {
?>
<div class="customize-toggle-control">
<div class="toggle-switch">
<input type="checkbox"
id="<?php echo esc_attr( $this->id ); ?>"
<?php $this->link(); ?>
<?php checked( $this->value() ); ?>
/>
<label for="<?php echo esc_attr( $this->id ); ?>">
<span class="toggle-track"></span>
<span class="toggle-thumb"></span>
</label>
</div>
<span class="customize-control-title">
<?php echo esc_html( $this->label ); ?>
</span>
<?php if ( $this->description ) : ?>
<span class="description">
<?php echo esc_html( $this->description ); ?>
</span>
<?php endif; ?>
</div>
<?php
}
}
Data Sanitization and Validation
// Common sanitization callbacks
// Text field
'sanitize_callback' => 'sanitize_text_field',
// Textarea
'sanitize_callback' => 'sanitize_textarea_field',
// Email
'sanitize_callback' => 'sanitize_email',
// URL
'sanitize_callback' => 'esc_url_raw',
// HTML
'sanitize_callback' => 'wp_kses_post',
// Number
'sanitize_callback' => 'absint',
// Hex color
'sanitize_callback' => 'sanitize_hex_color',
// Checkbox
'sanitize_callback' => function( $checked ) {
return ( ( isset( $checked ) && true == $checked ) ? true : false );
},
// Select/Radio
'sanitize_callback' => function( $input, $setting ) {
$input = sanitize_key( $input );
$choices = $setting->manager->get_control( $setting->id )->choices;
return ( array_key_exists( $input, $choices ) ? $input : $setting->default );
},
// Custom sanitization function
function mytheme_sanitize_select( $input, $setting ) {
// Get list of valid choices
$choices = $setting->manager->get_control( $setting->id )->choices;
// Return input if valid, otherwise return default
return ( array_key_exists( $input, $choices ) ? $input : $setting->default );
}
// Validation example
'sanitize_callback' => function( $value ) {
// Validate number range
$value = absint( $value );
if ( $value < 10 ) {
return 10;
} elseif ( $value > 100 ) {
return 100;
}
return $value;
},
Contextual Controls
// Show control only on specific pages
$wp_customize->add_control( 'homepage_hero_text', array(
'label' => __( 'Hero Text', 'mytheme' ),
'section' => 'homepage_options',
'type' => 'textarea',
'active_callback' => 'is_front_page', // Only show on homepage
) );
// Custom active callback
$wp_customize->add_control( 'sidebar_position', array(
'label' => __( 'Sidebar Position', 'mytheme' ),
'section' => 'layout',
'type' => 'radio',
'choices' => array(
'left' => 'Left',
'right' => 'Right',
'none' => 'No Sidebar',
),
'active_callback' => function() {
// Show only if sidebar is enabled
return get_theme_mod( 'show_sidebar', true );
},
) );
// Dependent controls
$wp_customize->add_control( 'header_style', array(
'label' => __( 'Header Style', 'mytheme' ),
'section' => 'header',
'type' => 'select',
'choices' => array(
'default' => 'Default',
'centered' => 'Centered',
'minimal' => 'Minimal',
),
) );
$wp_customize->add_control( 'header_sticky', array(
'label' => __( 'Sticky Header', 'mytheme' ),
'section' => 'header',
'type' => 'checkbox',
'active_callback' => function() {
// Only show if header style is not minimal
return get_theme_mod( 'header_style' ) !== 'minimal';
},
) );
Using Customizer Values in Your Theme
// In your theme templates
<?php
// Get customizer value with default
$header_color = get_theme_mod( 'header_background_color', '#ffffff' );
$footer_text = get_theme_mod( 'footer_text', 'Copyright © 2024' );
$logo_url = get_theme_mod( 'custom_logo' );
?>
<!-- Apply in HTML -->
<header style="background-color: <?php echo esc_attr( $header_color ); ?>">
<!-- Header content -->
</header>
<footer>
<p class="copyright"><?php echo esc_html( $footer_text ); ?></p>
</footer>
<?php
// Output CSS in wp_head
add_action( 'wp_head', function() {
$primary_color = get_theme_mod( 'primary_color', '#0073aa' );
$font_size = get_theme_mod( 'font_size', '16' );
?>
<style type="text/css">
:root {
--primary-color: <?php echo esc_attr( $primary_color ); ?>;
--font-size: <?php echo absint( $font_size ); ?>px;
}
.site-header {
background-color: <?php echo esc_attr( get_theme_mod( 'header_bg', '#fff' ) ); ?>;
}
body {
font-size: var(--font-size);
}
a {
color: var(--primary-color);
}
</style>
<?php
} );
?>
✅ Customizer Best Practices
- Organize logically:Group related settings in sections
- Use descriptive labels:Clear, concise control labels
- Provide defaults:Always set sensible default values
- Sanitize everything:Never trust user input
- Use postMessage:For instant feedback when possible
- Add descriptions:Help users understand settings
- Test responsively:Ensure changes work on all devices
- Minimize options:Too many options overwhelm users
- Use native controls:When possible, use WordPress core controls
- Document settings:Provide help text and tooltips
Practice Exercise
Hands-On Practice