More Forms Snippets
Multi Email Input — Recipient Chips HTML CSS JS
Multi Email Input · Forms · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Multi Email Input — Validated Recipient Chips with Paste-to-Split

Any "invite teammates," "share with," or "CC" field needs to accept several email addresses and treat each as a distinct, removable token — not a comma-soup string the user has to edit by hand. This snippet builds that multi-email recipient input in plain HTML, CSS, and vanilla JavaScript: type or paste addresses, each becomes a chip, invalid ones are flagged in red, duplicates are dropped, and the send button gates on a clean list.
Chips instead of a comma string
As the user types and presses Enter, comma, or semicolon, the current text is committed as a chip. Each chip shows the address with a remove (✕) button, so editing the list means clicking one token — not finding and deleting the right substring in a long comma-separated value. This is the interaction pattern everyone knows from email clients' To/CC fields, and it's far less error-prone than a raw text field for entering several values.
Paste a whole list, get a whole list
The most valuable behavior is paste handling: commit() splits the input on commas, semicolons, spaces, *and* newlines, so pasting "[email protected], [email protected]; [email protected]" or a column of addresses copied from a spreadsheet instantly becomes three chips. This is the difference between a usable invite field and one where the user has to add addresses one at a time — and it's the feature most hand-built multi-inputs forget.
Per-chip validation, not all-or-nothing
Each address is validated against a pragmatic email regex as it's added, and invalid ones render as red chips you can see and fix individually rather than a single "one or more emails are invalid" error that doesn't tell you which. The send button disables while any invalid chip exists and shows the valid count ("Send 3 invites"), and the hint line names exactly how many are wrong. Validating per-token is what lets the user correct a single typo'd address in a list of twenty without re-entering everything.
Dedupe and backspace-to-edit
Adding an address that's already present is silently ignored (case-insensitively), so pasting overlapping lists doesn't create duplicate invites. And pressing Backspace in an empty input removes the last chip — the standard quick-edit gesture from real recipient fields — so correcting the most recent entry doesn't require reaching for the mouse.
Focus and click behavior that feels native
The whole box acts like one input: clicking anywhere in it (including the empty space around the chips) focuses the text field, and a focus ring wraps the entire control rather than just the inner input, so it reads as a single cohesive field. A pending value is also committed on blur, so clicking away doesn't silently lose a half-typed address.
Accessible and ready to wire up
Each remove button has an aria-label, chips carry the full address as a title for truncated values, and the component exposes the clean valid list on send — the single hook where you'd POST the invites to your API. For full accessibility you'd announce additions/removals via an ARIA live region (covered in the FAQs), but the structure is correct and keyboard-operable from the start.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Rather than tracing the tokenizing logic by eye, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the commit() function's split regex handles a pasted block of addresses separated by mixed commas, semicolons, spaces, and newlines, and why addEmail() checks for an existing case-insensitive match before pushing a new chip. The same assistant can help optimize it, for example checking whether rebuilding the entire chipsEl.innerHTML on every keystroke could get slow with hundreds of recipients, or whether the EMAIL_RE regex is too permissive or too strict for real-world addresses. It's also handy for extending the input: ask it to add a hard cap on the number of recipients with a friendly message, integrate an autocomplete suggestion list from existing contacts, or announce added/removed chips through an ARIA live region for screen reader users. 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 "multi-email recipient input" in plain HTML, CSS, and JavaScript using only a text input and rendered chip elements — no tag-input library.
Requirements:
- A bordered box containing rendered chips for already-added emails plus a plain text input for typing the next one; clicking anywhere in the empty space of the box must focus the text input.
- Pressing Enter, comma, or semicolon while typing must commit the current text as a new chip and clear the input; pressing Backspace while the input is empty must remove the most recently added chip instead.
- Pasting text into the input must be intercepted (preventing the raw paste) and instead split on commas, semicolons, whitespace, and newlines, adding one chip per resulting non-empty piece — so a comma-separated list or a newline-separated column copied from a spreadsheet both expand into individual chips in one paste.
- Before adding any address, check it case-insensitively against already-added addresses and silently skip it if it's a duplicate.
- Validate every added address against a practical email regex and visually distinguish invalid ones (e.g. a red-tinted chip) from valid ones, without removing them automatically.
- Each chip needs its own remove (x) button that deletes just that entry and re-renders.
- A submit/send button must stay disabled whenever there are zero valid addresses or at least one invalid address present, and must display the current valid count when enabled (e.g. "Send 3 invites"); a status line below the input must name how many addresses are currently invalid when that's blocking submission.
- Losing focus on the input while it still has unsubmitted text must commit that text as a chip rather than silently discarding it.Step by step
How to Use
- 1Paste HTML, CSS, and JSAn invite field renders. Type an email and press Enter or comma to turn it into a chip.
- 2Paste a listPaste several addresses separated by commas, spaces, or newlines — each becomes its own chip at once.
- 3See invalid flaggingAdd a malformed address — it shows as a red chip, and the send button disables until it's fixed or removed.
- 4Remove and editClick a chip's ✕ to remove it, or press Backspace in the empty input to delete the last one.
- 5SendWith a valid, duplicate-free list, the button shows the count ("Send 3 invites") and submits the clean array.
- 6Wire up your APIIn the send handler, POST the valid email array to your invite endpoint instead of the demo confirmation.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
The paste handler intercepts the clipboard text and runs it through commit(), which splits on commas, semicolons, spaces, and newlines — so a comma-separated string or a column copied from a spreadsheet both expand into individual chips. Each split piece is validated and deduped as it's added, so a messy pasted list becomes a clean set of recipient chips.
Per-token validation lets the user see and fix exactly which address is wrong (a red chip) without disturbing the others — critical when entering many recipients, where re-typing the whole list to correct one typo is unacceptable. A single field-level "invalid" error can't point at the offending address among twenty.
Add an ARIA live region (aria-live="polite") that announces "Added [email protected]" and "Removed [email protected]" as chips change, give the input an aria-describedby pointing at the hint, and ensure each remove button's aria-label includes the address it removes. The chips and buttons are already keyboard-operable; the live region conveys the dynamic changes.
Add a max check in addEmail() that ignores new entries (and shows a message) once emails.length reaches your limit, and reflect the remaining count in the hint. For per-plan limits, pass the cap in and disable the input once reached.
In React, hold the emails array in useState and render chips with .map(), handling key events to commit/remove; in Vue, use a reactive array with v-for; in Angular, use a component array with *ngFor. The validation, paste-split, and dedupe functions are plain JavaScript that port unchanged — only the per-change re-render moves into the framework.