webdevpuneet.com

Email Composer — Free HTML CSS JS Form Snippet

Email Composer · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="emc">
  <div class="emc-bar">
    <span>New message</span>
    <div class="emc-winbtns"><i></i><i></i><i></i></div>
  </div>

  <div class="emc-field">
    <label for="emcTo">To</label>
    <div class="emc-chips" id="emcChips">
      <input type="text" id="emcTo" placeholder="Add recipients…" aria-label="Recipients">
    </div>
    <button class="emc-cc" id="emcCcBtn" type="button">Cc</button>
  </div>

  <div class="emc-field emc-ccrow" id="emcCcRow">
    <label for="emcCc">Cc</label>
    <input class="emc-plain" type="text" id="emcCc" placeholder="Carbon copy…" aria-label="Cc recipients">
  </div>

  <div class="emc-field">
    <label for="emcSub">Subject</label>
    <input class="emc-plain" type="text" id="emcSub" placeholder="Subject" aria-label="Subject">
  </div>

  <textarea class="emc-body" id="emcBody" placeholder="Write your message…" aria-label="Message body"></textarea>

  <div class="emc-foot">
    <div class="emc-tools">
      <button type="button" title="Bold"><b>B</b></button>
      <button type="button" title="Italic"><i>I</i></button>
      <button type="button" title="Attach file">
        <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
      </button>
      <button type="button" title="Insert link">
        <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
      </button>
    </div>
    <div class="emc-send-group">
      <span class="emc-status" id="emcStatus"></span>
      <button class="emc-send" id="emcSend" type="button" disabled>
        Send
        <svg viewBox="0 0 24 24" aria-hidden="true"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
      </button>
    </div>
  </div>
</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

Recipient chip input
Enter, comma, Tab, and blur all commit typed text into removable pills inserted before the flexing input.
Inline validation flags
Invalid addresses become red .bad chips with tooltips instead of vanishing — typos stay visible and fixable.
Paste-to-chips parsing
Intercepted paste splits clipboard text on commas, semicolons, and whitespace into individual validated chips.
Backspace chip deletion
Backspace in the empty input removes the last chip through the same code path as its × button.
Duplicate rejection
The recipients array is the source of truth; repeated addresses never create a second chip.
Smart send gating
Send enables only with at least one valid recipient and body text — red chips alone don't unlock it.
Collapsible Cc row
The Cc field stays hidden until toggled, then autofocuses — the progressive disclosure Gmail uses.
Send lifecycle states
Disabled → Sending… → Sent ✓ → full composer reset models the optimistic-send flow end to end.

About this UI Snippet

Email Composer — Gmail-Style Compose Window with Recipient Chips and Validation

Screenshot of the Email Composer snippet rendered live

The compose window is one of the most interaction-dense forms on the web, and its heart is the recipient chip input: type an address, press Enter or comma, and it solidifies into a removable pill — invalid ones flagged red, duplicates rejected, pasted lists split automatically, and Backspace on an empty field deleting the last chip. This component builds a complete Gmail-style composer around that input in HTML, CSS, and vanilla JavaScript, with a collapsible Cc row, formatting toolbar, live send-button gating, and a sending → sent status cycle.

The chip input mechanics

The To field is a flex-wrap container holding chips plus a borderless text input that flexes to fill the remaining space (flex: 1; min-width: 120px), so typing always happens "after" the chips and wraps to new lines naturally as chips accumulate. Three commit triggers convert text into a chip: Enter, comma, and Tab-with-content (all with preventDefault so the comma never lands in the input and Tab doesn't escape the field mid-entry); blur also commits, so half-typed addresses aren't silently lost when the user clicks into Subject. Each chip is inserted with insertBefore(chip, toInput) so the input stays last.

Validation and duplicate rejection

Every committed address is tested against a pragmatic email regex — anything shaped [email protected]. Failures still become chips, but with a .bad class rendering them red with an "Invalid address" tooltip: keeping invalid entries visible (rather than refusing them) matches Gmail's behaviour and lets users spot typos instead of wondering where their input went. The recipients array is the single source of truth; duplicate strings are rejected before a chip is ever created, and each chip's remove button splices its entry back out.

Paste-to-chips

Pasting "[email protected], [email protected]; [email protected]" into a naive input yields one useless string. The composer intercepts paste, reads the clipboard text, splits on commas, semicolons, and whitespace with one regex, and runs each token through addChip() — so pasting a list from a spreadsheet or another email instantly produces validated chips. This is the single highest-value behaviour in any recipient field and takes six lines.

Send gating and the status cycle

The Send button stays disabled until the form is actually sendable: at least one *valid* recipient and a non-empty body (checked via recipients.some(r => r.valid) — red chips alone don't count). Clicking Send disables the button, shows "Sending…", then "Sent ✓", then resets the whole composer — chips removed, array cleared, fields emptied — modelling the full optimistic-send lifecycle you would wire to your mail API. The Cc row is hidden until its toggle is clicked, and focuses its input on reveal, exactly as Gmail defers rarely-used fields.

Small details that sell it

Chips animate in with a quick scale-fade; the remove buttons carry per-chip aria-labels ("Remove [email protected]") for screen readers; Backspace-on-empty reuses the remove button's own click handler so there is exactly one deletion code path; and the toolbar buttons are real focusable buttons with tooltips, ready to wire to document.execCommand alternatives or a rich-text engine like the rich text editor snippet.

Customisation

Wire Send to your backend (the recipients array and field values are all you need for the payload), extend the chip logic to the Cc row by calling the same setup on it, swap the indigo accent, and connect the attach button to a file dropzone. For address book suggestions while typing, combine the input with the mention autocomplete pattern.

Step by step

How to Use

  1. 1
    Paste the HTML, CSS, and JSA compose window renders with a dark title bar, To field, hidden Cc row, Subject, message body, formatting toolbar, and a disabled Send button.
  2. 2
    Add recipientsType an address and press Enter, comma, or Tab — it becomes an indigo chip. Type something invalid and the chip turns red with an "Invalid address" tooltip.
  3. 3
    Paste a listPaste several addresses separated by commas, semicolons, or spaces — they split into individual validated chips instantly.
  4. 4
    Edit the chip setClick a chip's × to remove it, or press Backspace in the empty input to pop the last chip. Duplicates are silently rejected.
  5. 5
    Reveal Cc and composeClick Cc to expand the carbon-copy row (it autofocuses); Send enables only once a valid recipient and body text both exist.
  6. 6
    SendClick Send — the status runs "Sending…" then "Sent ✓" and the composer resets; replace the timeout with your mail API call.

Real-world uses

Common Use Cases

Webmail and inbox apps
The compose pane for a mail client — pairs with the email inbox list for a full mail UI.
Team invite modals
The chip input alone is the standard "invite teammates by email" control; drop it in a modal.
CRM and outreach tools
Compose sequences to many validated recipients with paste-from-spreadsheet support.
Share dialogs
Share-by-email in docs and dashboards — compare the share modal pattern.
Support ticket replies
Agent reply forms with Cc for escalation and send-state feedback.
Learning chip inputs
A reference implementation of multi-value email entry — also see the simpler multi email input and generic tag input.
Related: Ghost Text Inline Autocomplete Input
See the Ghost Text Inline Autocomplete Input for a related forms pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

The To field is a flex-wrap container where chips and the input are siblings; the input has flex: 1 with min-width: 120px, so it always claims the leftover space on the current line and wraps below when chips fill a row. New chips are inserted with insertBefore(chip, toInput), keeping the input as the last child — typing therefore always continues after the most recent chip, exactly like Gmail.

Silently refusing input makes users think the field is broken, and blocking commits mid-typing interrupts flow. Following Gmail, the composer accepts the entry, marks it .bad (red with an "Invalid address" tooltip), and simply excludes it from send-gating — recipients.some(r => r.valid) requires at least one good address. The user sees the typo, clicks ×, and retypes, with no lost work.

A paste listener calls preventDefault, reads e.clipboardData.getData('text'), and splits it with /[,;\s]+/ — one regex covering commas, semicolons, and any whitespace including newlines. Each token runs through the same addChip() used for typed entries, so validation and duplicate rejection apply uniformly. Pasting a column of emails from a spreadsheet produces a clean chip per address.

Everything you need is already collected: recipients (filter to r.valid ones), the Cc input's value, subject.value, and bodyEl.value. In the click handler, replace the first setTimeout with fetch('/api/send', { method: 'POST', body: JSON.stringify(payload) }), keep the "Sending…" state while awaiting, show "Sent ✓" on success, and restore the button with an error message on failure instead of resetting.

Hold recipients as an array in state and render chips with a map — addChip and remove become state updates, and insertBefore disappears since the framework controls order. Keyboard handling moves to onKeyDown on the input; the paste handler stays but pushes to state. Derive the Send disabled prop from the same validity check. The flex-wrap chip layout and animations are pure CSS and port unchanged to any framework, Angular included.