Multi-Selector

Web component demo

<multi-selector> is a web component that lets a user select multiple options from a drop-down menu:

  • Arbitrarily nested groups of options
  • Searchable groups/values
  • Keyboard navigation
  • Reads options from JSON or markup
  • Adaptive styling with dark mode support
  • VanillaJS (no dependencies)

This page contains several examples of how to use this web component. View on GitHub.

Contents

Load from JSON

Supply the data as JSON using the src attribute of <multi-selector>.

Expected data structure

Each item should follow this JSON structure:

{ "label": "Display text", // optional
"value": "unique_id", // required
"children": [] // optional for leaf nodes }

See fallacies.json for an example. When no label is provided, the value serves as the fallback display text.

Preselecting options

Add "selected": true to any leaf node to preselect it. This property is ignored on group nodes since their selection state derives from their children.

Simplified data formats

<multi-selector> automatically converts simpler formats:

  • Flat arrays: ["option1", "option2"]
  • Nested objects: {"Group": ["item1", "item2"]}

See albums.json and countries.json for examples of alternative formats.

Code

  <!-- html -->
  <multi-selector
    src="data/countries.json"
    name="countries">
  </multi-selector>

Define options in markup

Populate <multi-selector> using standard <option> tags. Each option uses its text content as the value unless you provide a value attribute.

Dynamically adding options to the DOM will update the component automatically.

Note that DOM updates trigger a complete component re-render.

Grouping with optgroup

Use <optgroup> tags to create option groups. This component supports nested <optgroup> elements, extending beyond the standard HTML behavior.

Group labels and values default to the element's textContent, but can be overridden with label and value attributes.

Add a color to the color selector:

Code

  <!-- html -->
  <multi-selector name="colors">
    <option>Red</option>
    <option>Yellow</option>
    <option>Green</option>
    <option>Blue</option>
  </multi-selector>

Code

  <!-- html -->
  <multi-selector name="instruments">
  <optgroup label="String instruments" value="string">
      <optgroup label="Guitar" value="guitar">
          <option value="acoustic">Acoustic</option>
          <option value="electric">Electric</option>
      </optgroup>
      <option value="violin">Violin</option>
      <option value="cello">Cello</option>
  </optgroup>
  [...]
  </multi-selector>

Controls & navigation

  • Select or deselect entire groups using group checkboxes
  • Filter options by keyword using the search input
  • Toggle the [val] button to search values only (excluding group names)

Filtering and folding

  • Show only selected options: click or press Ctrl+\
  • Clear all filters: click or press Ctrl+/
  • Fold or unfold all groups: use +/- buttons or press Ctrl+[/Ctrl+]

Keyboard navigation

  • Navigate with , Home, End, and Tab
  • Close the dropdown with Esc

Code

  <!-- html -->
  <multi-selector
    src="data/fallacies.json"
    name="fallacies">
  </multi-selector>

Component behavior and events

Control component state with the disabled attribute. Use placeholder to provide helpful text when no selections are made.

Change events

The component dispatches change events whenever selections change. The event's detail property contains an array of selected values. Open the browser console to see the event data structure.

Code

  <!-- html -->
  <multi-selector
    name="Select genres"
    src="data/genres.json"
    placeholder=
      "What are your favorite media genres?"
    disabled>
  </multi-selector>

Form integration

<multi-selector> works seamlessly with standard HTML forms. It participates in form submission and validation like any native form control.

Form data is submitted as a JSON string containing the selected values. For programmatic access, use the selectedValues property which returns a live array.

Set the name attribute to specify the form field name.

Form data

Code

  <!-- html -->
  <form>
    <label for="Albums">Captain Beefheart</label>
    <multi-selector
      src="data/albums.json"
      name="Albums"
      placeholder=
        "Select Captain Beefheart albums...">
    </multi-selector>
    <button type="submit">submit</button>
  </form>

Styling and theming

The component adapts to its environment by inheriting text color and using transparent backgrounds. It automatically derives border colors and interactive states from the inherited color.

Dark mode support

Set mode="dark" or let the component automatically detect system preference using prefers-color-scheme.

Styling approaches

Customize appearance using ::part() selectors for precise control, or CSS custom properties for simple theming:

  • ::part(container) - Main dropdown container
  • ::part(display) - Selected items display area
  • ::part(options) - Options list container
  • --ms-* properties - Colors, spacing, and dimensions

See the CSS reference for all available custom properties.

Code

  /* css */
  .styling-example multi-selector {
    --ms-text-color: hsl(0, 5%, 84%);
    --ms-border-color: hsl(120, 24%, 55%);
    --ms-dropdown-background: hsl(120, 24%, 25%);
    --ms-search-background: hsl(120, 24%, 35%);
    --ms-hover: hsl(120, 24%, 30%);
    --ms-search-placeholder-text-color: hsl(0, 5%, 68%);
    width: 300px;
    margin: 0;
  }
  .styling-example multi-selector::part(container) {
    border-width: 2px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  }
  .styling-example multi-selector::part(dropdown) {
    background: var(--ms-dropdown-background);
  }
  .styling-example multi-selector::part(options) {
    border-radius: 8px;
    border: 1px solid hsl(120, 24%, 40%);
  }

Empty state and dynamic data

Components without initial data display an empty state message and remain disabled until data is provided.

Set or update data programmatically using the data property. Assign an array of objects or set to an empty array to clear all options.

Code

  <!-- html -->
  <button id="add-data">Add data</button>
  <button id="clear-data" disabled>Clear data</button>
  <multi-selector name="fruits"></multi-selector>
  // javascript
  const fruitSelector = document.querySelector("multi-selector[name='fruits']")
  const addDataButton = document.getElementById("add-data")
  const clearDataButton = document.getElementById("clear-data")

  const fruits = [
      { label: "Apple", value: "apple" },
      { label: "Banana", value: "banana" },
      { label: "Orange", value: "orange" },
      { label: "Grape", value: "grape" },
  ]

  addDataButton.addEventListener("click", () => {
      fruitSelector.data = fruits
      addDataButton.disabled = true
      clearDataButton.disabled = false
  })
  clearDataButton.addEventListener("click", () => {
      fruitSelector.data = []
      clearDataButton.disabled = true
      addDataButton.disabled = false
  })

CSS Reference

Complete reference for styling <multi-selector> components.

Custom Properties

Use these CSS custom properties for theming:

Layout & Spacing
  • --ms-height - Component height (default: calc(2rem + var(--ms-padding-block)))
  • --ms-max-height - Maximum dropdown height (default: 60vh)
  • --ms-padding-block - Vertical padding (default: .25em)
  • --ms-padding-inline - Horizontal padding (default: 1em)
  • --ms-border-radius - Border radius (default: 5px)
Base Colors
  • --ms-text-color - Main text color (default: inherit)
  • --ms-border-color - Border color (default: currentColor)
  • --ms-checkbox-color - Checkbox fill color (default: currentColor)
Derived Colors
  • --ms-text-color-disabled - Disabled text color
  • --ms-border-color-disabled - Disabled border color
Backgrounds
  • --ms-dropdown-background - Dropdown container background
  • --ms-search-background - Search input background
  • --ms-hover - Hover state background
Button States
  • --ms-button-background - Button background
  • --ms-button-background-hover - Button hover state
  • --ms-button-background-active - Button active state
Search Input
  • --ms-search-text-color - Search input text color
  • --ms-search-placeholder-text-color - Search placeholder color

Shadow Parts

Use ::part() selectors for structural styling:

  • ::part(container) - Main dropdown container (<details>)
  • ::part(display) - Selected items display area
  • ::part(controls) - Control button panel
  • ::part(control-button) - Individual control buttons
  • ::part(dropdown) - Dropdown content area
  • ::part(filter) - Filter/search section
  • ::part(search) - Search input field
  • ::part(options) - Options container

Theme Modes

The component automatically adapts to dark/light themes:

  • mode="light" - Force light theme
  • mode="dark" - Force dark theme
  • No mode attribute - Automatically follows prefers-color-scheme

Styling Priority

When both custom properties and ::part() rules target the same property, ::part() rules win due to higher CSS specificity. Use ::part() for precise control and custom properties for theming.

Alternatives