Tom Select

Enhance a native select with search, tagging, remote data and more via Tom Select.

Basic example

TomSelectField enhances an EXISTING native <select> with Tom Select - searchable, taggable, remote-loading, dependent-field wiring, a clear button, drag-drop reorder. Opt in with the tomselected class.

Dropdown menus share the Material 3 Expressive appearance: rounded surfaces, tertiary selection colors, and an inset focus indicator for arrow-key navigation. Single and multiple selection use the same menu styling in light and dark themes.

<div class="input-field">
  <select id="tomselect-example-1" class="tomselected" data-select-clear="" placeholder="Choose your option">
    <option value="" disabled selected></option>
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
    <option value="3">Option 3</option>
  </select>
  <label for="tomselect-example-1">Searchable select</label>
</div>

<div class="input-field">
  <select id="tomselect-example-2" class="tomselected" data-select-tag="" multiple>
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
    <option value="3">Option 3</option>
  </select>
  <label for="tomselect-example-2">Taggable multi-select</label>
</div>

Setup

Start with the kmaterialize CSS and JavaScript from Getting started, then follow the component setup below.

Tom Select is an optional peer dependency, loaded on demand - install it yourself: npm install tom-select.

import 'tom-select/dist/css/tom-select.css';
import 'kmaterialize/dist/css/materialize.css';

If you also call FormSelect.init(...) yourself elsewhere (rather than relying on AutoInit()), exclude .tomselected from that call too, so a select is never initialized by both components.


  document.addEventListener('DOMContentLoaded', function() {
    FormSelect.init(document.querySelectorAll('select:not(.tomselected)'), {});
    TomSelectField.init(document.querySelectorAll('select.tomselected'), {
      // specify options here
    });
  });
        

Coexistence with FormSelect: a .tomselected select is never picked up by FormSelect's own AutoInit selector - it's excluded the same way .no-autoinit is. If you initialize both manually (as this docs site does, since it never calls the global AutoInit()), make sure your own FormSelect.init(...) call also excludes .tomselected.

Examples and variants

Placeholder
<select id="tom-placeholder" class="tomselected" placeholder="Search a country">
  <option value=""></option>
  <option value="fr">France</option>
  <option value="jp">Japan</option>
</select>
<label for="tom-placeholder">Country</label>
Multiple values selected
<select id="tom-multiple" class="tomselected" multiple>
  <option value="html" selected>HTML</option>
  <option value="css" selected>CSS</option>
  <option value="typescript">TypeScript</option>
</select>
<label for="tom-multiple">Skills</label>
Tag creation
<select id="tom-tags" class="tomselected"
  data-select-tag="" data-select-clear="Clear tags" multiple>
  <option value="documentation">Documentation</option>
  <option value="frontend">Frontend</option>
</select>
<label for="tom-tags">Tags</label>
Clearable
<select id="tom-clear" class="tomselected" data-select-clear="Clear selection">
  <option value=""></option>
  <option value="high" selected>High</option>
  <option value="medium">Medium</option>
</select>
<label for="tom-clear">Priority</label>
Disabled
<select id="tom-disabled" class="tomselected" disabled>
  <option value="archived" selected>Archived</option>
</select>
<label for="tom-disabled">Status</label>
Rounded image options
<select id="tom-icons" class="tomselected icons">
  <option value="">Choose a person</option>
  <option value="kevin" data-icon="/images/kevin.png">Kevin</option>
  <option value="yuna" data-icon="/images/yuna.jpg">Yuna</option>
</select>
<label for="tom-icons">Assignee</label>
Outlined
<div class="input-field outlined">
  <select id="tom-outlined" class="tomselected">
    <option value="">Choose a type</option>
    <option value="feature">Feature</option>
    <option value="bug">Bug</option>
  </select>
  <label for="tom-outlined">Issue type</label>
</div>
Outlined with multiple values
<div class="input-field outlined">
  <select id="tom-outlined-multiple" class="tomselected" multiple>
    <option value="design" selected>Design</option>
    <option value="frontend" selected>Frontend</option>
    <option value="backend">Backend</option>
    <option value="testing">Testing</option>
  </select>
  <label for="tom-outlined-multiple">Departments</label>
</div>
Many values: wrap
<select id="tom-many-values" class="tomselected"
  data-select-overflow="wrap" multiple>
  <option value="html" selected>HTML</option>
  <option value="css" selected>CSS</option>
  <option value="sass" selected>Sass</option>
  <option value="typescript" selected>TypeScript</option>
  <option value="vite" selected>Vite</option>
  <option value="accessibility" selected>Accessibility</option>
  <option value="testing" selected>Testing</option>
  <option value="documentation" selected>Documentation</option>
</select>
<label for="tom-many-values">Technologies</label>
Many values: horizontal scroll
<select id="tom-many-values-scroll" class="tomselected"
  data-select-overflow="scroll" multiple>
  <option value="html" selected>HTML</option>
  <option value="css" selected>CSS</option>
  <option value="sass" selected>Sass</option>
  <option value="typescript" selected>TypeScript</option>
  <option value="vite" selected>Vite</option>
  <option value="accessibility" selected>Accessibility</option>
  <option value="testing" selected>Testing</option>
  <option value="documentation" selected>Documentation</option>
</select>
<label for="tom-many-values-scroll">Technologies</label>

Validation

Keep constraints on the original select and connect its error message with aria-describedby. Your application should also update the generated control’s accessible error state when applying validation after initialization.

These previews keep their error state visible. In your form, show errors after checking a field or submitting, then clear them when the value is valid.

For text fields, use .input-field.error, aria-invalid="true", and a message linked with aria-describedby. Remove the error state when resolved. Use aria-live="polite" for messages updated dynamically.

Required searchable selection
Choose a department.
<div class="input-field outlined error">
    <select id="validation-tom-select" name="department" class="tomselected" required aria-invalid="true" aria-describedby="validation-tom-select-error">
        <option value="" selected disabled></option>
        <option value="design">Design</option>
        <option value="production">Production</option>
    </select>
    <label for="validation-tom-select">Department</label>
    <span id="validation-tom-select-error" class="supporting-text"><i class="material-icons" aria-hidden="true">error</i> Choose a department.</span>
</div>

Reference

Options

Name Type Default Description
remote Object undefined { url, value, label, search, dataKey } - fetches options remotely. Derived from data-select-remote (JSON) if not passed. label may be a render template.
settings Object undefined Raw Tom Select settings, merged in last - escape hatch for anything not covered by the dataset attributes below.

Per-select attributes, matching the CrazyPHP convention this component was ported from:

Attribute Description
data-select-tag Allows creating new options (tagging); also enables the drag-drop reorder plugin.
data-select-clear Adds a clear button, with this attribute's value as its title.
data-select-remote JSON-encoded { url, value, label, search, dataKey } remote data spec.
data-depends A selector for another element; this select's remote options reload when it changes.

Methods

All the methods are called on the plugin instance. You can get the plugin instance like this:


                  const instance = TomSelectField.getInstance(elem);
        
.destroy();

Destroy plugin instance and teardown the Tom Select widget.


instance.destroy();
      

Properties

Name Type Description
el Element The native <select> the plugin was initialized with.
ready Promise<void> Resolves once Tom Select has loaded and the widget has been created.
tomSelect TomSelect The underlying Tom Select instance, once ready resolves.