More Forms Snippets
Tag Input — Free HTML CSS JS Snippet
Tag / Chip Input · Forms · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Tag Input — Enter to Add, Duplicate Check & Backspace Remove

A tag input lets users type and add multiple tags or labels — used in filter selectors, skill lists, email address fields, and keyword inputs (the multi-select dropdown is the picker-based cousin). This snippet implements the full tag interaction: Enter to add, comma to add, duplicate prevention, click-to-remove, and backspace-to-undo.
Adding tags
The input listens for keydown. On Enter or , (comma), addTag(input.value) is called and the input is cleared. addTag(v) trims whitespace, checks for duplicates via querySelectorAll('.tag').map(...), and returns early if the tag already exists or is empty.
The tag element
Each tag is a <span class="tag"> with the tag text and a <span class="remove">×</span> button. The remove button's onclick calls tag.remove() — removing just that span from the DOM.
Backspace to remove last tag
When the input is empty and the user presses Backspace, the last tag is removed: document.querySelectorAll('.tag').at(-1)?.remove(). This matches the behaviour of email address inputs in Gmail and similar applications.
The Enter/comma add pattern
The tag input listens for keydown events. When Enter or comma is pressed, it calls addTag() which trims the input value, checks for duplicates, adds the tag to the array, creates a chip element, and clears the input. Comma detection uses e.key === ',' with e.preventDefault() to stop the comma character appearing in the input after the tag is added.
The backspace-last-remove pattern
When the input is empty (input.value === '') and Backspace is pressed, the last tag in the array is removed and the corresponding chip element is deleted from the DOM. This matches the standard tag input interaction that users expect from email address fields, filter chips, and CRM contact selectors.
Duplicate prevention
Before adding a tag, the current tags array is checked: if (tags.includes(newTag)) return. Show a brief shake animation on the existing duplicate chip to communicate "already added" without an error message. Add .shake { animation: shake 0.3s } to flash the duplicate chip briefly.
Connecting to form submission
Collect tag values for form submission: document.querySelector('form').addEventListener('submit', e => { e.preventDefault(); const values = tags; submitForm(values); }). Or use a hidden input to hold a JSON-encoded array: hiddenInput.value = JSON.stringify(tags). This makes the tag values available to standard HTML form submission without JavaScript preprocessing.
The Enter/comma add pattern
The tag input listens for keydown events. When Enter or comma is pressed, it calls addTag() which trims the input value, checks for duplicates, adds the tag to the array, creates a chip element, and clears the input. Comma detection uses e.key === ',' with e.preventDefault() to stop the comma character appearing in the input after the tag is added.
The backspace-last-remove pattern
When the input is empty (input.value === '') and Backspace is pressed, the last tag in the array is removed and the corresponding chip element is deleted from the DOM. This matches the standard tag input interaction that users expect from email address fields, filter chips, and CRM contact selectors.
Duplicate prevention
Before adding a tag, the current tags array is checked: if (tags.includes(newTag)) return. Show a brief shake animation on the existing duplicate chip to communicate "already added" without an error message. Add .shake { animation: shake 0.3s } to flash the duplicate chip briefly.
Connecting to form submission
Collect tag values for form submission: document.querySelector('form').addEventListener('submit', e => { e.preventDefault(); const values = tags; submitForm(values); }). Or use a hidden input to hold a JSON-encoded array: hiddenInput.value = JSON.stringify(tags). This makes the tag values available to standard HTML form submission without JavaScript preprocessing.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to trace the keydown branching by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why addTag rebuilds the existing-tags list from querySelectorAll and textContent on every call instead of keeping a separate array, and why the backspace-removes-last-tag behavior only fires when the input's value is empty. The same assistant can help optimize it — for instance whether reading the DOM to check duplicates becomes noticeably slower with dozens of tags compared to maintaining a plain JavaScript array as the source of truth, or whether the inline onclick handlers on the remove buttons should be replaced with a single delegated listener. It's also useful for extending the input: ask it to add autocomplete suggestions filtered as the user types, a maximum tag count with a visible counter, or paste-support that splits a comma-separated clipboard string into multiple tags at once. 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:
Build a "tag/chip input" field in plain HTML, CSS, and JavaScript using DOM-based duplicate checking — no framework, no external state array required.
Requirements:
- A styled container that looks like a single text input but actually holds a row of removable tag chips followed by a real text input, where clicking anywhere in the container focuses the inner input (flex-wrap layout so chips and the input flow naturally across lines).
- An addTag function that trims the given value, reads all currently rendered tag chips' text content (stripping the remove-button glyph) to build a duplicate-check list, and silently does nothing if the trimmed value is empty or already present — no error message, just a no-op.
- Pressing Enter or typing a comma inside the input must call addTag with the current input value, clear the input afterward, and prevent the comma character itself from being typed into the field.
- Each added tag must render as a small pill with the tag text and its own inline remove button; clicking that button must remove only that specific pill from the DOM.
- Pressing Backspace while the input is completely empty must remove the most recently added tag (the last chip in the list), giving a fast way to undo the last entry without touching the mouse.
- Provide a row of clickable suggestion buttons below the field that call the same addTag function with a preset value, so suggestions and manual typing share one single code path for adding a tag.
- New tags must play a small entrance animation (e.g. scaling and fading in) when they are added so the list doesn't feel like it's snapping.Step by step
How to Use
- 1Type and press EnterType a tag in the input and press Enter or comma to add it. Duplicate tags are rejected silently.
- 2Remove a tagClick the × on any tag to remove it from the list.
- 3Backspace to remove last tagWhen the input is empty, press Backspace to remove the most recently added tag.
- 4Update preset tagsIn the HTML panel, change the preset .tag spans to your default tags. Each needs a .remove span with onclick="this.parentNode.remove()".
- 5Limit tag countIn addTag(), add a check: if (existing.length >= 5) return; to limit the number of tags.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
addTag() calls querySelectorAll(".tag") and maps each element to its textContent with the × stripped. If the new value is already in this array, the function returns early without adding the tag.
const tags = [...document.querySelectorAll(".tag")].map(t => t.textContent.replace("×","").trim()); gives an array of tag strings. Append as JSON to a hidden form input or include directly in a fetch request body.
In addTag(), before creating the span: const count = document.querySelectorAll(".tag").length; if (count >= maxTags) return; — where maxTags is your limit.
Call addTag(value) from the dropdown item click handler. The duplicate check and DOM creation work the same way regardless of the trigger.
Yes. Click "JSX" for a React component. In React, manage tags as an array in useState. Add/remove by spreading: setTags(prev => [...prev, newTag]) and setTags(prev => prev.filter(t => t !== tag)).
Add a data-category attribute when creating the span. In CSS, use [data-category="design"] { background: #e0f2fe } etc. to colour-code tag categories.