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>

Chip Multiselect — Free HTML CSS JS Tag Token Input Field

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

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.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Instead of guessing why suggestion clicks sometimes seemed to "not work" in similar widgets you've built before, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why menu options use mousedown with preventDefault instead of a plain click handler, and how that interacts with the input's blur-triggered close() function. The same assistant can help optimize it — ask whether recomputing available() from scratch (filtering the whole ALL array) on every keystroke and every arrow-key press is wasteful for a much larger option list, and what a debounced or memoized approach would look like. It's also useful for extending the widget: ask it to support adding free-text values that aren't in the predefined list, cap the maximum number of selected chips, or persist the selected array to a hidden form field so it participates in a native form submission. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "chip multiselect" token input in plain HTML, CSS, and JavaScript — no framework, no library.

Requirements:
- A single flex container that visually behaves like one text field: it holds already-selected values rendered as removable chips (each with its own small × button) plus a text input that flows after the chips and wraps to a new line when the chips fill the row, and clicking anywhere in the container focuses the input.
- A dropdown menu of suggestions built from a fixed source array minus whatever is already selected, filtered by a case-insensitive substring match against the current input text, updating on every keystroke, and showing a distinct "no matches" row when the filtered list is empty.
- Full keyboard control on the input: ArrowDown and ArrowUp move a wrapping active-index highlight through the currently filtered suggestion list, Enter commits the currently highlighted suggestion as a new chip, and Backspace on an empty input removes the most recently added chip.
- Selecting a suggestion by mouse must use a mousedown handler with preventDefault (not a plain click handler) on each menu item, because the input's blur event — which closes the dropdown — fires before a click event would, so without preventDefault the menu would close before the click could register.
- Keep a single array as the source of truth for the current selection, and have both the chip rendering and the suggestion-menu rendering derive entirely from that array plus the current input text — no separate DOM-based bookkeeping of what's selected.
- Each chip must animate in with a brief scale-and-fade entrance when added, and each chip's remove button must visually indicate danger (e.g. turning red) on hover.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

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
A richer alternative to a tag input.
Search filters
Pick facets beside a filterable table.
Recipients
Address fields in a contact form.
Categories
Combine with a multi select dropdown.
Autocomplete
Single-pick variant: autocomplete input.
Profile editing
Skills and interests on a settings panel.
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.