Skip to main content

WordPress Theme Customizer

Duration: 45 minutes
Module 4: Session 4.4

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
The Customizer follows the "What You See Is What You Get" (WYSIWYG) principle, showing changes in real-time before they're published.

Core Customizer Features

👁️
Live Preview
📱
Responsive Preview
💾
Draft & Schedule
🔄
Selective Refresh
🎯
Contextual Controls
🔒
Permission Based

The Customizer Interface

Customizing
Site Identity
Colors
Header Image
Background Image
Menus
Widgets
Homepage Settings
Additional CSS

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
  1. Access the Customizer for your active theme
  2. Explore all default sections and controls
  3. Add a custom section for "Theme Options"
  4. Create a color picker for primary color
  5. Add a text field for footer copyright
  6. Implement an image upload for logo
  7. Set up postMessage transport for live preview
  8. Create a custom toggle switch control
  9. Add selective refresh for a widget area
  10. Export and import customizer settings

Additional Resources