Creating a custom multi-select dropdown enhances user experience and allows more flexibility than native <select> elements. Here’s how you can build one from scratch with JavaScript, CSS, and HTML:

Step 1: Analyze Requirements

  • Allow users to select multiple options from a dropdown.
  • Display selected options in an input field.
  • Persist selections across page refreshes using localStorage.

Step 2: HTML Structure

Define the basic structure for the dropdown:

html
1
<div id="multiSelect" class="multi-select-dropdown"></div>

Step 3: CSS Styling

Style the dropdown for a clean, modern UI:

css
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
.multi-select-dropdown {
  position: relative;
  width: 300px;
}
.dropdown-header {
  border: 1px solid #ccc;
  padding: 10px;
  cursor: pointer;
  background: white;
}
.dropdown-list {
  display: none;
  position: absolute;
  width: 100%;
  max-height: 200px;
  overflow-y: auto;
  border: 1px solid #ccc;
  border-top: none;
  background: white;
}
.dropdown-list.show {
  display: block;
}
.dropdown-item {
  padding: 8px;
  cursor: pointer;
}
.dropdown-item:hover {
  background-color: #f0f0f0;
}
.dropdown-item.selected {
  background-color: #e0e0e0;
}
.selected-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-bottom: 5px;
}
.tag {
  background-color: #f0f0f0;
  padding: 2px 5px;
  border-radius: 3px;
  display: flex;
  align-items: center;
}
.tag-remove {
  margin-left: 5px;
  cursor: pointer;
}

Step 4: JavaScript Logic

Implement the dynamic functionality:

js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
class MultiSelect {
  constructor(container, options) {
    this.container = container;
    this.options = options;
    this.selectedValues = [];
    this.render();
    this.setupEventListeners();
  }

  render() {
    // Create dropdown header
    const header = document.createElement('div');
    header.classList.add('dropdown-header');
    header.textContent = 'Select options';

    // Create selected tags container
    const selectedTags = document.createElement('div');
    selectedTags.classList.add('selected-tags');

    // Create dropdown list
    const list = document.createElement('div');
    list.classList.add('dropdown-list');

    this.options.forEach(option => {
      const item = document.createElement('div');
      item.classList.add('dropdown-item');
      item.textContent = option.label;
      item.dataset.value = option.value;
      list.appendChild(item);
    });

    this.container.appendChild(selectedTags);
    this.container.appendChild(header);
    this.container.appendChild(list);
  }

  setupEventListeners() {
    const header = this.container.querySelector('.dropdown-header');
    const list = this.container.querySelector('.dropdown-list');
    const selectedTags = this.container.querySelector('.selected-tags');

    // Toggle dropdown
    header.addEventListener('click', () => {
      list.classList.toggle('show');
    });

    // Select/deselect items
    list.addEventListener('click', e => {
      if (e.target.classList.contains('dropdown-item')) {
        const value = e.target.dataset.value;
        const label = e.target.textContent;

        if (this.selectedValues.some(item => item.value === value)) {
          // Deselect
          this.selectedValues = this.selectedValues.filter(
            item => item.value !== value
          );
          e.target.classList.remove('selected');
        } else {
          // Select
          this.selectedValues.push({ value, label });
          e.target.classList.add('selected');
        }

        this.updateSelectedTags();
      }
    });

    // Remove tag
    selectedTags.addEventListener('click', e => {
      if (e.target.classList.contains('tag-remove')) {
        const valueToRemove = e.target.closest('.tag').dataset.value;
        this.selectedValues = this.selectedValues.filter(
          item => item.value !== valueToRemove
        );
        this.updateSelectedTags();
        this.updateListSelection();
      }
    });

    // Close dropdown when clicking outside
    document.addEventListener('click', e => {
      if (!this.container.contains(e.target)) {
        list.classList.remove('show');
      }
    });
  }

  updateSelectedTags() {
    const selectedTags = this.container.querySelector('.selected-tags');
    const header = this.container.querySelector('.dropdown-header');

    // Clear existing tags
    selectedTags.innerHTML = '';

    // Create new tags
    this.selectedValues.forEach(item => {
      const tag = document.createElement('div');
      tag.classList.add('tag');
      tag.dataset.value = item.value;
      tag.innerHTML = `
              ${item.label}
              <span class="tag-remove">×</span>
          `;
      selectedTags.appendChild(tag);
    });

    // Update header text
    header.textContent = this.selectedValues.length
      ? `${this.selectedValues.length} selected`
      : 'Select options';
  }

  updateListSelection() {
    const list = this.container.querySelector('.dropdown-list');
    list.querySelectorAll('.dropdown-item').forEach(item => {
      const value = item.dataset.value;
      if (this.selectedValues.some(selected => selected.value === value)) {
        item.classList.add('selected');
      } else {
        item.classList.remove('selected');
      }
    });
  }

  // Optional method to get selected values
  getSelectedValues() {
    return this.selectedValues.map(item => item.value);
  }
}

// Example usage
const options = [
  { value: '1', label: 'JavaScript' },
  { value: '2', label: 'TypeScript' },
  { value: '3', label: 'React' },
  { value: '4', label: 'Angular' },
  { value: '5', label: 'Svelve' },
  { value: '6', label: 'Vue' },
];

const multiSelect = new MultiSelect(
  document.getElementById('multiSelect'),
  options
);

This code defines a class named MultiSelect that creates and manages a custom multi-select dropdown using vanilla JavaScript. It allows users to select multiple options from a dropdown and displays their selections with tags. Here’s a breakdown of the key functionality:

Class Constructor

js
1234567
constructor(container, options) {
  this.container = container;
  this.options = options;
  this.selectedValues = [];
  this.render();
  this.setupEventListeners();
}
  • container: A DOM element where the dropdown will be rendered.
  • options: An array of objects, each representing a dropdown option (value and label).
  • selectedValues: Tracks the currently selected items.

Calls:

  • render(): Renders the dropdown structure.
  • setupEventListeners(): Adds interactivity to the dropdown.

render Method

This method creates the visual structure of the dropdown:

  • Header: Displays the dropdown title or number of selected items.
  • Selected Tags Container: Displays tags for selected items.
  • Dropdown List: Lists all the available options dynamically from the options array.

Each option is rendered as a div with a data-value attribute containing the option’s unique value.

setupEventListeners Method

Adds event listeners to enable dropdown functionality:

1. Toggle Dropdown:

js
123
header.addEventListener('click', () => {
  list.classList.toggle('show');
});

Clicking the header toggles the visibility of the dropdown list.

2. Select/Deselect Items:

js
12345
list.addEventListener("click", (e) => {
  if (e.target.classList.contains("dropdown-item")) {
    ...
  }
});

Clicking an option:

  • Adds or removes it from selectedValues.
  • Toggles the selected class on the item.
  • Updates the tags using updateSelectedTags().

3. Remove Tags:

js
12345
selectedTags.addEventListener("click", (e) => {
  if (e.target.classList.contains("tag-remove")) {
    ...
  }
});

Clicking the remove icon (×) on a tag:

  • Removes the corresponding option from selectedValues.
  • Updates the tags and list selection.

4. Close Dropdown on Outside Click:

js
12345
document.addEventListener('click', e => {
  if (!this.container.contains(e.target)) {
    list.classList.remove('show');
  }
});

updateSelectedTags Method

Updates the selected tags displayed below the dropdown header:

  • Clears existing tags.
  • Adds a new tag for each selected item with a remove button.
  • Updates the header text to reflect the number of selected items or reset to “Select options.”

updateListSelection Method

Synchronizes the dropdown list items with the current selections:

  • Adds the selected class to items in selectedValues.
  • Removes it from unselected items.

Optional getSelectedValues Method

Returns an array of the value properties of the currently selected options.

Example Usage

js
12345678910111213
const options = [
  { value: '1', label: 'JavaScript' },
  { value: '2', label: 'TypeScript' },
  { value: '3', label: 'React' },
  { value: '4', label: 'Angular' },
  { value: '5', label: 'Svelte' },
  { value: '6', label: 'Vue' },
];

const multiSelect = new MultiSelect(
  document.getElementById('multiSelect'),
  options
);
  • options: An array of programming languages/frameworks.
  • The dropdown is initialized on the container with the ID multiSelect.

Summary of Features

  • Allows multiple selections with tags to display selected options.
  • Dynamic rendering of dropdown options.
  • Toggleable dropdown list.
  • Click-to-select and deselect functionality for items.
  • Option to remove tags via a close (×) button.
  • Syncs UI with current selections.

This is a clean, reusable component that enhances usability by providing a multi-select dropdown built with pure JavaScript.

Full code

html
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Custom Multi-Select Dropdown</title>
    <style>
      .multi-select-dropdown {
        position: relative;
        width: 300px;
        margin: 10rem auto;
      }
      .dropdown-header {
        border: 1px solid #ccc;
        padding: 10px;
        cursor: pointer;
        background: white;
      }
      .dropdown-list {
        display: none;
        position: absolute;
        width: 100%;
        max-height: 200px;
        overflow-y: auto;
        border: 1px solid #ccc;
        border-top: none;
        background: white;
      }
      .dropdown-list.show {
        display: block;
      }
      .dropdown-item {
        padding: 8px;
        cursor: pointer;
      }
      .dropdown-item:hover {
        background-color: #f0f0f0;
      }
      .dropdown-item.selected {
        background-color: #e0e0e0;
      }
      .selected-tags {
        display: flex;
        flex-wrap: wrap;
        gap: 5px;
        margin-bottom: 5px;
      }
      .tag {
        background-color: #f0f0f0;
        padding: 2px 5px;
        border-radius: 3px;
        display: flex;
        align-items: center;
      }
      .tag-remove {
        margin-left: 5px;
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <div id="multiSelect" class="multi-select-dropdown"></div>
    <script>
      class MultiSelect {
        constructor(container, options) {
          this.container = container;
          this.options = options;
          this.selectedValues = [];
          this.render();
          this.setupEventListeners();
        }

        render() {
          // Create dropdown header
          const header = document.createElement('div');
          header.classList.add('dropdown-header');
          header.textContent = 'Select options';

          // Create selected tags container
          const selectedTags = document.createElement('div');
          selectedTags.classList.add('selected-tags');

          // Create dropdown list
          const list = document.createElement('div');
          list.classList.add('dropdown-list');

          this.options.forEach(option => {
            const item = document.createElement('div');
            item.classList.add('dropdown-item');
            item.textContent = option.label;
            item.dataset.value = option.value;
            list.appendChild(item);
          });

          this.container.appendChild(selectedTags);
          this.container.appendChild(header);
          this.container.appendChild(list);
        }

        setupEventListeners() {
          const header = this.container.querySelector('.dropdown-header');
          const list = this.container.querySelector('.dropdown-list');
          const selectedTags = this.container.querySelector('.selected-tags');

          // Toggle dropdown
          header.addEventListener('click', () => {
            list.classList.toggle('show');
          });

          // Select/deselect items
          list.addEventListener('click', e => {
            if (e.target.classList.contains('dropdown-item')) {
              const value = e.target.dataset.value;
              const label = e.target.textContent;

              if (this.selectedValues.some(item => item.value === value)) {
                // Deselect
                this.selectedValues = this.selectedValues.filter(
                  item => item.value !== value
                );
                e.target.classList.remove('selected');
              } else {
                // Select
                this.selectedValues.push({ value, label });
                e.target.classList.add('selected');
              }

              this.updateSelectedTags();
            }
          });

          // Remove tag
          selectedTags.addEventListener('click', e => {
            if (e.target.classList.contains('tag-remove')) {
              const valueToRemove = e.target.closest('.tag').dataset.value;
              this.selectedValues = this.selectedValues.filter(
                item => item.value !== valueToRemove
              );
              this.updateSelectedTags();
              this.updateListSelection();
            }
          });

          // Close dropdown when clicking outside
          document.addEventListener('click', e => {
            if (!this.container.contains(e.target)) {
              list.classList.remove('show');
            }
          });
        }

        updateSelectedTags() {
          const selectedTags = this.container.querySelector('.selected-tags');
          const header = this.container.querySelector('.dropdown-header');

          // Clear existing tags
          selectedTags.innerHTML = '';

          // Create new tags
          this.selectedValues.forEach(item => {
            const tag = document.createElement('div');
            tag.classList.add('tag');
            tag.dataset.value = item.value;
            tag.innerHTML = `
                    ${item.label}
                    <span class="tag-remove">×</span>
                `;
            selectedTags.appendChild(tag);
          });

          // Update header text
          header.textContent = this.selectedValues.length
            ? `${this.selectedValues.length} selected`
            : 'Select options';
        }

        updateListSelection() {
          const list = this.container.querySelector('.dropdown-list');
          list.querySelectorAll('.dropdown-item').forEach(item => {
            const value = item.dataset.value;
            if (
              this.selectedValues.some(selected => selected.value === value)
            ) {
              item.classList.add('selected');
            } else {
              item.classList.remove('selected');
            }
          });
        }

        // Optional method to get selected values
        getSelectedValues() {
          return this.selectedValues.map(item => item.value);
        }
      }

      // Example usage
      const options = [
        { value: '1', label: 'JavaScript' },
        { value: '2', label: 'TypeScript' },
        { value: '3', label: 'React' },
        { value: '4', label: 'Angular' },
        { value: '5', label: 'Svelve' },
        { value: '6', label: 'Vue' },
      ];

      const multiSelect = new MultiSelect(
        document.getElementById('multiSelect'),
        options
      );
    </script>
  </body>
</html>

Conclusion

Creating a custom multi-select dropdown with vanilla JavaScript provides a powerful and flexible solution for enhancing user interfaces without relying on external libraries. By dynamically rendering elements, managing user interactions, and updating the UI in real time, this approach ensures a seamless and interactive experience. It also gives you full control over customization and functionality, making it adaptable to various use cases. Whether you’re building a simple form or a complex application, mastering this technique is a valuable skill that showcases the versatility of JavaScript.