webdevpuneet.com

Chip Multiselect — Free HTML CSS JS Tag Token Input Field

Chip Multiselect · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="cm-field" id="cmField">
  <label class="cm-label">Skills</label>
  <div class="cm-box" id="cmBox">
    <span class="cm-chips" id="cmChips"></span>
    <input type="text" class="cm-input" id="cmInput" placeholder="Add a skill…" autocomplete="off" />
    <ul class="cm-menu" id="cmMenu" role="listbox"></ul>
  </div>
  <p class="cm-hint">Type to filter · Enter to add · Backspace to remove</p>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Removable chips
Each selection is a token with an × button.
Filtered suggestions
Case-insensitive substring match as you type.
Deduplicated
Selected items leave the available pool.
Arrow-key highlight
Up/Down move an active option, wrapping.
Enter to add
Commits the highlighted suggestion.
Backspace to remove
Empty input deletes the last chip.
mousedown select
Adds before blur closes the menu.
Single source of truth
A selected array drives all rendering.

About this UI Snippet

Chip Multiselect — A Token Input With Filtered Suggestions

Screenshot of the Chip Multiselect snippet rendered live

The chip multiselect is the token-style input used for tags, skills, recipients, and categories — each choice becomes a removable chip inside the field, and a filtered dropdown suggests what's left to add as you type. This snippet builds a fully keyboard-operable one with plain HTML, CSS, and vanilla JavaScript, with no dependency.

Chips inside a flex field

The field is a flex container with flex-wrap that holds the chips and a flexible text input that grows to fill the remaining space and drops to the next line when chips fill the row. Clicking anywhere in the box focuses the input, so the whole control behaves like one field. Each chip animates in with a small scale pop and carries a remove button that turns red on hover — the standard token affordances.

A filtered, deduplicated dropdown

As you type, available() returns the source options minus anything already selected and filtered by a case-insensitive substring match, so the menu only ever offers valid, new choices. The menu opens on focus and re-renders on every keystroke, showing a "No matches" row when nothing fits. Because selection removes an item from the available pool, you can't add a duplicate.

Full keyboard control

The input handles a complete keyboard model: Arrow Down and Up move an active highlight through the filtered list (wrapping at the ends), Enter adds the highlighted option, and Backspace on an empty input removes the last chip — the behaviour people expect from Gmail's recipient field. Suggestion clicks use mousedown with preventDefault so the selection happens before the input's blur would close the menu, a subtle but essential detail for click-to-add dropdowns.

State drives the DOM

A selected array is the single source of truth; renderChips() and renderMenu() rebuild their sections from it after every change. Keeping state separate from the DOM means adding, removing, and filtering are simple array operations, and the rendered chips and menu always reflect the current selection.

Focus styling

The box shows a focus ring (accent border plus soft shadow) whenever the input is focused, matching a good text input, and a hint line documents the keyboard shortcuts. These small touches make the control feel finished and discoverable.

Customizing it

Swap the option list for your own data or an async source, allow free-text entries that aren't in the list, cap the number of selections, or restyle the chips. Read selected on submit to get the chosen values. Pair it with a tag input, an autocomplete input, or a multi select dropdown.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSAn empty skills field renders with a hint.
  2. 2
    Focus and typeA dropdown filters the remaining options.
  3. 3
    Press EnterThe highlighted option becomes a chip.
  4. 4
    Press BackspaceAn empty input removes the last chip.
  5. 5
    Click a chip ×That selection is removed and re-offered.
  6. 6
    Swap the optionsReplace the ALL array with your data.

Real-world uses

Common Use Cases

Tagging fields
Build a richer alternative to a tag input, where every selection becomes a removable chip and chosen items leave the suggestion pool.
Search facet selection
Pick several facets beside a filterable table, using case-insensitive substring matching as the user types into the field.
Recipient fields
Address a contact form to several people, with arrow keys moving an active option that wraps at either end.
Category pickers
Combine with a multi-select dropdown for large category lists, or switch to an autocomplete input for a single choice.
Skills and interests editing
Let people edit their skills on a settings panel, with each chip carrying an x button for removal.
Related: Searchable Combobox with Keyboard Navigation
See the Searchable Combobox with Keyboard Navigation for a related forms pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

The available function returns the source options minus anything already in the selected array, then filters by a case-insensitive substring match on what you have typed. Because chosen items leave the available pool, the dropdown only ever offers valid new choices and you cannot add the same value twice.

When you click a suggestion, the input would blur first and close the menu before a click handler runs. Using mousedown with preventDefault commits the selection before blur fires, so click-to-add works reliably. It is a subtle but essential detail for any dropdown that closes on blur.

Arrow Down and Up move an active highlight through the filtered list and wrap at the ends, Enter adds the highlighted option, and Backspace on an empty input removes the last chip. This matches the recipient-field behaviour people know from email clients, so the control is fully usable without a mouse.

A selected array is the single source of truth. After any add or remove, renderChips and renderMenu rebuild their sections from that array, so the chips and the available suggestions always reflect the current selection. Keeping state separate from the DOM makes every operation a simple array update.

Hold the selected array and the query in state and derive the filtered suggestions with useMemo / computed rather than manual DOM rendering. Map selected to chips and the filtered list to options in your template, and handle the same keydown logic on the input. Use onMouseDown with preventDefault for option clicks so blur does not close the menu first.