Multi Email Input — Recipient Chips HTML CSS JS

Multi Email Input · Forms · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Address-to-chip tokenization
Enter, comma, or semicolon commits the current text as a removable chip — like a real To/CC field.
Paste-to-split
Pasting a list splits on commas, semicolons, spaces, and newlines, turning a whole list into chips at once.
Per-chip validation
Each address is validated individually and invalid ones flag red, so a single typo is fixable without re-entering the list.
Case-insensitive dedupe
Adding an address already present is silently ignored, so overlapping pastes never create duplicate invites.
Backspace-to-remove
Backspace in an empty input deletes the last chip — the standard quick-edit gesture from recipient fields.
Gated send with count
The send button disables while any invalid chip exists and shows the valid count, with a hint naming how many are wrong.
Whole-box focus behavior
Clicking anywhere in the box focuses the input, with a focus ring around the entire control for a cohesive field.
Commit-on-blur
A half-typed address is committed when the field loses focus, so clicking away never silently loses it.

About this UI Snippet

Multi Email Input — Validated Recipient Chips with Paste-to-Split

Screenshot of the Multi Email Input snippet rendered live

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:

text
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

  1. 1
    Paste HTML, CSS, and JSAn invite field renders. Type an email and press Enter or comma to turn it into a chip.
  2. 2
    Paste a listPaste several addresses separated by commas, spaces, or newlines — each becomes its own chip at once.
  3. 3
    See invalid flaggingAdd a malformed address — it shows as a red chip, and the send button disables until it's fixed or removed.
  4. 4
    Remove and editClick a chip's ✕ to remove it, or press Backspace in the empty input to delete the last one.
  5. 5
    SendWith a valid, duplicate-free list, the button shows the count ("Send 3 invites") and submits the clean array.
  6. 6
    Wire 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

Team and workspace invites
The "invite teammates" field in onboarding or settings — pair with a conditional form fields flow for roles.
Share and collaboration dialogs
Add multiple people to a shared document or project in a share modal.
Email composer To/CC/BCC
Recipient fields in a mail or messaging compose view.
Bulk notification recipients
Enter several addresses to send an announcement or report to.
Waitlist and referral invites
Let users invite friends by email, complementing a waitlist signup referral flow.
Learning tokenized inputs
A reference for chip tokenization, paste-splitting, and per-token validation — compare with a tag input for the generic-tag version.

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.