Create an Image Select Control for the WordPress Customizer

Image select field in the Customizer

I recently found myself in need of an image select field in the Customizer. The Customizer already has some great options built in like radio boxes, select boxes, and so on. But I wanted images to give users a more visual representation of their choices.

File structure

Before we get started, I’ll give you a quick idea of the file structure I have going on.

I’m working inside a custom theme, of course. All of my Customizer code is going inside /inc/customizer/. Within that folder, I have a few more things going on:

  • /inc/customizer/

    • class-amanda-customizer.php — Where all my customizer settings and registrations go. I have “Amanda” in there since this is for my Amanda theme.
    • controls/class-ng-image-select-control.php — Class where we set up our custom control. This is the main thing we’ll be creating here.
    • css/image-select.css — Some custom CSS for our control.
    • js/control-image-select.js — JavaScript that handles saving our custom control.
    • images/ — Where all your images are saved (the ones you use in the settings).

Setting up the main Customizer class

This is the class that will eventually store all your Customizer settings. If you followed along with the previous tutorial then you’ll want to add to that file. You’ll need to add the action for including controls, along with the include_controls() method.

Here’s what I’ve got so far:

<?php
class Amanda_Customizer {

	/**
	 * Amanda_Customizer constructor.
	 *
	 * @param string $theme_slug
	 *
	 * @access public
	 * @since  1.1
	 * @return void
	 */
	public function __construct() {

		add_action( 'customize_register', array( $this, 'include_controls' ) );
		add_action( 'customize_register', array( $this, 'register_customize_sections' ) );

	}

	/**
	 * Include Custom Controls
	 *
	 * Includes all our custom control classes.
	 *
	 * @param WP_Customize_Manager $wp_customize
	 *
	 * @access public
	 * @since  1.1
	 * @return void
	 */
	public function include_controls( $wp_customize ) {

		require_once get_template_directory() . '/inc/customizer/controls/class-ng-image-select-control.php';

		$wp_customize->register_control_type( 'NG_Image_Select_Control' );

	}

	/**
	 * Add all panels and sections to the Customizer
	 *
	 * @param WP_Customize_Manager $wp_customize
	 *
	 * @access public
	 * @since  1.1
	 * @return void
	 */
	public function register_customize_sections( $wp_customize ) {

		// Create sections
		$wp_customize->add_section( 'blog_layout', array(
			'title'    => __( 'Blog Layout', 'amanda' ),
			'priority' => 101
		) );

		// Populate sections
		$this->blog_layout_section( $wp_customize );

	}

	/**
	 * Section: Blog Layout
	 *
	 * @param WP_Customize_Manager $wp_customize
	 *
	 * @access private
	 * @since  1.1
	 * @return void
	 */
	private function blog_layout_section( $wp_customize ) {

		/* Our image select setting will go here */

	}

}

At this point, we’re:

  • Including our custom control file (which we’ll create next).
  • Creating a new section for “Blog Layout”.
  • Setting up the area where we’ll add settings to the blog layout section (we’ll do this later).

This file should be included via functions.php (or similar) like so:

/**
 * Customizer settings.
 */
require get_template_directory() . '/inc/customizer/class-amanda-customizer.php';
new Amanda_Customizer();

Build the control class

Next let’s work on actually creating that class we included (/inc/customizer/controls/class-ng-image-select-control.php).

Here’s how it looks:

<?php
class NG_Image_Select_Control extends WP_Customize_Control {

	/**
	 * The type of customize control being rendered.
	 *
	 * @access public
	 * @since  1.1
	 * @var    string
	 */
	public $type = 'radio-image';

	/**
	 * Add our JavaScript and CSS to the Customizer.
	 *
	 * @access public
	 * @since  1.1
	 * @return void
	 */
	public function enqueue() {
		wp_enqueue_script( 'ng-image-select-control', get_template_directory_uri() . '/inc/customizer/js/control-image-select.js', array( 'jquery' ), false, true );
		wp_enqueue_style( 'ng-image-select', get_template_directory_uri() . '/inc/customizer/css/image-select.css' );
	}

	/**
	 * Add custom JSON parameters to use in the JS template.
	 *
	 * @access public
	 * @since  1.1
	 * @return void
	 */
	public function to_json() {
		parent::to_json();

		// Create the image URL. Replaces the %s placeholder with the URL to the customizer /images/ directory.
		foreach ( $this->choices as $value => $args ) {
			$this->choices[ $value ]['url'] = esc_url( sprintf( $args['url'], get_template_directory_uri() . '/inc/customizer/images/' ) );
		}

		$this->json['choices'] = $this->choices;
		$this->json['link']    = $this->get_link();
		$this->json['value']   = $this->value();
		$this->json['id']      = $this->id;
	}

	/**
	 * An Underscore (JS) template for this control's content.
	 *
	 * Class variables for this control class are available in the `data` JS object;
	 * export custom variables by overriding {@see WP_Customize_Control::to_json()}.
	 *
	 * @see    WP_Customize_Control::print_template()
	 *
	 * @access protected
	 * @since  1.1
	 * @return void
	 */
	protected function content_template() {
		?>
		<# if ( ! data.choices ) {
			return;
		} #>

		<# if ( data.label ) { #>
			<span class="customize-control-title">{{ data.label }}</span>
		<# } #>

		<# if ( data.description ) { #>
			<span class="description customize-control-description">{{{ data.description }}}</span>
		<# } #>

		<# for ( key in data.choices ) { #>

			<label for="{{ data.id }}-{{ key }}">
				<span class="screen-reader-text">{{ data.choices[ key ]['label'] }}</span>
				<input type="radio" value="{{ key }}" name="_customize-{{ data.type }}-{{ data.id }}" id="{{ data.id }}-{{ key }}" {{{ data.link }}} <# if ( key === data.value ) { #> checked="checked" <# } #> />
				<img src="{{ data.choices[ key ]['url'] }}" alt="{{ data.choices[ key ]['label'] }}" />
			</label>
		<# } #>
		<?php
	}

}

Here’s what’s going on:

  • The $type property is where we tell WordPress what type of control this is.
  • The enqueue() method adds our two asset files to the Customizer (our CSS file and our JS file—to be created).
  • The to_json() method ensures that we pass along the image URL to the JavaScript template. This will make sense later.
  • The content_template() method is where we build the template that will actually be displayed in the Customizer. The important thing here is rendering the image.

The content template is written as an Underscore JS template so you may not be familiar with the format. But if you look towards the end, you can probably read out the general layout of our image select. It’ll look a bit like this in the end:

<label>
    <span class="screen-reader-text">Hidden description of the image. Only seen by screen readers.</span>
    <input type="radio" value="value"> <!-- This will be hidden in CSS, but we need it for actually setting values. -->
    <img src="Image URL here" alt="Image description here"> <!-- Actual image -->
</label>

Knowing the layout will help you understand what our JavaScript and CSS are doing.

JavaScript

Now we just need a small JavaScript snippet to save our settings. So go ahead and create the control-image-select.js file. This goes in /inc/customizer/js/. Here’s how it looks:

wp.customize.controlConstructor['radio-image'] = wp.customize.Control.extend({

    ready: function() {
        var control = this;
        var value = (undefined !== control.setting._value) ? control.setting._value : '';

        this.container.on( 'change', 'input:radio', function() {
            value = jQuery( this ).val();
            control.setting.set( value );
            // refresh the preview
            wp.customize.previewer.refresh();
        });
    }

});

This watches for whenever our radio changes value. When it does, it saves that new value to the Customizer settings then triggers a refresh on the live preview.

CSS

We need a bit of CSS. Our CSS will achieve two main things:

  1. Hide the actual radio inputs. We don’t want the settings to look like radio buttons.
  2. Add some sort of styling to the currently selected image.

Create the CSS file: /inc/customizer/css/image-select.css and add this:

/* Hides the radio button */
.customize-control-radio-image input[type="radio"] {
    visibility: hidden;
    position: absolute;
}

/* Makes images look clickable */
.customize-control-radio-image img {
    border: 4px solid transparent;
    box-sizing: border-box;
    cursor: pointer;
    height: auto;
    max-width: 100%;
    padding: 1px;
}

/* Adds blue border around currently selected image */
.customize-control-radio-image input:checked + img {
    border-color: #00a0d2;
}

The CSS is well commented to help you understand what each chunk is for.

Adding your actual setting!

Now we have all the structure in place for our control. We just need to use it somewhere.

Open up /inc/customizer/class-amanda-customizer.php again. This is the file we created in the first step.

Find where we added this bit from before:

/**
 * Section: Blog Layout
 *
 * @param WP_Customize_Manager $wp_customize
 *
 * @access private
 * @since  1.1
 * @return void
 */
private function blog_layout_section( $wp_customize ) {

	/* Our image select setting will go here */

}

Now we’re going to be adding our setting inside of there, where the comment is. Here’s how it looks now:

/**
 * Section: Blog Layout
 *
 * @param WP_Customize_Manager $wp_customize
 *
 * @access private
 * @since  1.1
 * @return void
 */
private function blog_layout_section( $wp_customize ) {

	/* Layout */
	$wp_customize->add_setting( 'layout_style', array(
		'default'           => 'default',
		'sanitize_callback' => 'sanitize_key'
	) );
	$wp_customize->add_control( new NG_Image_Select_Control( 'layout_style', array(
		'label'       => esc_html__( 'Layout', 'amanda' ),
		'description' => __( 'Choose a layout for the blog posts.', 'amanda' ),
		'section'     => 'blog_layout',
		'settings'    => 'layout_style',
		'choices'     => array(
			'default' => array(
				'label' => esc_html__( 'One column with featured images centered', 'amanda' ),
				'url'   => '%sblog-default.png'
			),
			'list'    => array(
				'label' => esc_html__( 'One column with featured images aligned to the left', 'amanda' ),
				'url'   => '%sblog-list.png'
			),
			'grid'    => array(
				'label' => esc_html__( 'Two column grid layout with featured images centered', 'amanda' ),
				'url'   => '%sblog-grid.png'
			)
		),
		'priority'    => 10
	) ) );

}

Here are some important things to note:

  • Use new NG_Image_Select_Control to create the control. This is the control class we created in the class-ng-image-select-control.php file.
  • You need to include an array of ‘choices’. The key values are what will be saved in the database (in my case, 'default', 'list', and 'grid'). Each key should be assigned to yet another array with two more keys: 'label' (the hidden description text and alt text) and 'url' (the URL to the image).
  • Each image URL should be prefixed with %s. This is a placeholder for the URL to the /images/ folder we created (in /inc/customizer/images/). After that, you can just insert the file name.

Final Result

Put it all together and what do you get?

Image select field in the Customizer

Photo of Ashley
I'm a 30-something California girl living in England (I fell in love with a Brit!). My three great passions are: books, coding, and fitness. more »

Don't miss my next post!

Sign up to get my blog posts sent directly to your inbox (plus exclusive store discounts!).

You might like these

7 comments

  1. Fatal error: Uncaught Error: Call to a member function get_setting() on string in /var/www/public/wp-includes/class-wp-customize-control.php:218 Stack trace: #0 /var/www/public/wp-content/themes/underscores/inc/customizer/class-amanda-customizer.php(93): WP_Customize_Control->__construct(‘layout_style’, Array) #1 /var/www/public/wp-content/themes/underscores/inc/customizer/class-amanda-customizer.php(57): Amanda_Customizer->blog_layout_section(Object(WP_Customize_Manager)) #2 /var/www/public/wp-includes/class-wp-hook.php(298): Amanda_Customizer->register_customize_sections(Object(WP_Customize_Manager)) #3 /var/www/public/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters(NULL, Array) #4 /var/www/public/wp-includes/plugin.php(453): WP_Hook->do_action(Array) #5 /var/www/public/wp-includes/class-wp-customize-manager.php(734): do_action(‘customize_regis…’, Object(WP_Customize_Manager)) #6 /var/www/public/wp-includes/class-wp-hook.php(298): WP_Customize_Manager->wp_loaded(”) #7 /var/www/public/wp-includes/class-wp-h in /var/www/public/wp-includes/class-wp-customize-control.php on line 218

    1. add $wp_customize on your control parameter :
      $wp_customize->add_control( new NG_Image_Select_Control( $wp_customize, 'layout_style', array(

  2. Hello Ashley,

    Great Work!! However, there is some missing parameter on your class NG_Image_Select_Control. This code will solve it :

    $wp_customize->add_control( new NG_Image_Select_Control( $wp_customize, 'layout_style', array(

    Hope this will help other

    Best Regards,

  3. Hi Ashley,

    Really good post for customizer custom control.

    I am trying to implementing same code in my theme but getting error message : Fatal error: Class ‘WP_Customize_Control’ not found in (…….)

    Can you tell me solution for above error message.

Recent Posts

    Random Posts