Search
Select2 with Image: How to Add Images to Dropdown Options

Select2 with Image: How to Add Images to Dropdown Options

The fastest way to build Select2 with image options is to store each image path in a data-image attribute on the <option>, then pass one template function to both templateResult and templateSelection. The function returns a jQuery object that holds an <img> and the label, so Select2 renders it instead of escaping it. You then react to choices with the select2:select and select2:unselect events.

The problem is that Select2 prints only the text of each option. Put an <img> tag inside the option label and it shows up as raw text or gets stripped. Many examples fix the dropdown list but forget the closed box that shows the chosen item. Almost none explain what to do when the user removes a selection.

This guide walks through the full setup in four steps: marking up the options, writing the templates, wiring select and deselect events, and loading images from remote data. The code is plain jQuery, so it works in a static page or a Laravel Blade view the same way. Copy each block as you go.

What you need before you start

You need jQuery, Select2 4.x, a few small images, and a <select> element. Everything else is a template function you write yourself.

Requirements and files

Load jQuery before Select2, or the plugin will not register. Keep your images small, around 20 to 32 pixels square, because the browser requests each one when the list opens.

<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>

How Select2 renders an option

By default Select2 shows only the text of each option and passes any string you return through escapeMarkup, which strips HTML. Two hooks control the output. templateResult draws each row in the open list, and templateSelection draws the chosen item in the closed box.

If a hook returns a jQuery object, Select2 inserts it as is. That is what makes images possible, and it also means you must escape user-supplied text yourself. Build elements with .text() and .attr() instead of gluing strings together.

Return a jQuery object, not an HTML string, and Select2 renders your image instead of escaping it.

Step 1. Add image paths to your options

Mark each option with a data-image attribute and decide which Select2 options you need. The markup stays valid HTML, and the page still works if the script fails to load.

Add a data-image attribute to each option

The empty first option lets the placeholder work on a single select.

<select id="country" style="width: 100%">
  <option></option>
  <option value="us" data-image="/img/flags/us.png">United States</option>
  <option value="ca" data-image="/img/flags/ca.png">Canada</option>
  <option value="mx" data-image="/img/flags/mx.png">Mexico</option>
</select>
<img id="preview" alt="" width="32">

Pick the Select2 options you need

These are the Select2 options that matter for an image dropdown.

Option

What it does

placeholder

Shows hint text. A single select needs an empty first <option>.

allowClear

Adds an X button that clears a single select.

width

'100%' makes the control fill its container.

templateResult

Renders each row in the dropdown.

templateSelection

Renders the chosen item in the box.

minimumResultsForSearch

Infinity hides the search box.

closeOnSelect

false keeps the list open in a multi-select.

One data-image attribute per option is all the markup a Select2 with image dropdown needs.

Step 2. Render images in the list and selection

Write one template function and use it for both hooks. That way the image appears in the open list and in the chosen item, which keeps the control consistent.

Write the template function

The !state.id check matters. Select2 passes the placeholder and the "Searching..." message through the same function, and they have no id or image.

function formatImage(state) {
  if (!state.id) {
    return state.text;
  }
  var src = $(state.element).data('image');
  return $('<span class="s2-item"></span>')
    .append($('<img class="s2-img" alt="">').attr('src', src))
    .append($('<span></span>').text(state.text));
}

$('#country').select2({
  placeholder: 'Choose a country',
  allowClear: true,
  width: '100%',
  templateResult: formatImage,
  templateSelection: formatImage
});

Add multiple to the <select> and the same code renders an image inside each selected tag. Select2 searches the text property only, so typing still matches the label and the images never interfere with filtering.

Style the images with CSS

Without styles the image sits above or beside the text at its natural size. A flex row fixes the alignment in both the list and the selection box.

.s2-item { display: flex; align-items: center; gap: 8px; }
.s2-img { width: 20px; height: 20px; object-fit: cover; border-radius: 3px; }

Use the same template for templateResult and templateSelection, and the list and the chosen item always match.

Step 3. Handle select and deselect events

Select2 fires its own events on the original <select>. Use select2:select to react to a choice and select2:unselect to react to a removal. Both give you the data object in e.params.data.

Listen for select and unselect

Because the data object keeps a reference to the original option, you can read data-image again inside the handler. This example updates a preview image next to the control.

$('#country')
  .on('select2:select', function (e) {
    var image = $(e.params.data.element).data('image');
    $('#preview').attr('src', image);
  })
  .on('select2:unselect', function (e) {
    console.log('Removed:', e.params.data.id);
  })
  .on('select2:clear', function () {
    $('#preview').attr('src', '');
  });

Use select2:unselect for tags removed from a multi-select, and select2:clear for the X button on a single select. The "ing" variants fire first and let you cancel the action with e.preventDefault().

Event

Fires when

Cancelable

select2:selecting

Just before an option is chosen

Yes

select2:select

After an option is chosen

No

select2:unselecting

Just before a selection is removed

Yes

select2:unselect

After a selection is removed

No

select2:clear

After the X button clears the control

No

Set or clear a selection from code

To select an option in code, set the value and trigger change. To deselect everything, set the value to null.

$('#country').val('ca').trigger('change');
$('#country').val(null).trigger('change');

Neither line fires select2:select or select2:unselect, because those events come from user actions. If your handler must run, call the same function yourself after the change. Trigger change.select2 instead when you only want to refresh the Select2 display without waking other change listeners.

val().trigger('change') updates the control but never fires select2:select, so call your handler yourself.

Step 4. Load images from data or Ajax

When options come from an array or an API, the image lives on the data object, not on an <option> element. Your template reads state.image first and falls back to the attribute.

Use remote results with images

Return an image field from processResults. The template below works for remote results, for data arrays, and for options preselected in the HTML.

function formatUser(state) {
  if (!state.id) {
    return state.text;
  }
  var src = state.image || $(state.element).data('image');
  return $('<span class="s2-item"></span>')
    .append($('<img class="s2-img" alt="">').attr('src', src))
    .append($('<span></span>').text(state.text));
}

$('#user').select2({
  width: '100%',
  ajax: {
    url: '/api/users',
    dataType: 'json',
    delay: 250,
    data: function (params) { return { q: params.term }; },
    processResults: function (data) {
      return { results: data.map(function (u) {
        return { id: u.id, text: u.name, image: u.avatar };
      }) };
    }
  },
  templateResult: formatUser,
  templateSelection: formatUser
});

Fix common problems

Most failures come from a handful of causes. Check these first.

  • Image shows as text: you returned an HTML string. Return a jQuery object.

  • No image in the chosen item: you set only templateResult. Add templateSelection.

  • Dropdown cut off inside a modal: set dropdownParent: $('#myModal').

  • Search box missing: remove minimumResultsForSearch: Infinity.

  • Broken image: check that the path is correct from the page URL, not from your JavaScript file.

  • Preview on hover: Select2 has no highlight event. Listen for mouseenter on .select2-results__option with delegated binding.

For Ajax data, read state.image, and keep the data-image fallback for preselected values.

Ship your Select2 image dropdown

You now have the full pattern: a data-image attribute on each option, one template function for the list and the selection, a few lines of CSS, and handlers for select2:select and select2:unselect. The same function handles remote data once you read state.image.

Test three paths before you ship: pick an option, remove it, and reload with a preselected value. If all three show the right image, you are done. For more step-by-step jQuery tutorials and Laravel code you can paste into a project, browse the guides at WebJourney.

Tags

  • Share This: