webdevpuneet.com

Email Inbox — Mail List UI with Read, Star & Search

Email Inbox · Layouts · Plain HTML, CSS & JS · Live preview

CategoryLayouts

Copy & paste

Source Code

<div class="ei">
  <div class="ei-bar">
    <label class="ei-all"><input type="checkbox" id="eiAll"><span>Select</span></label>
    <input type="search" class="ei-search" id="eiSearch" placeholder="Search mail…" aria-label="Search">
    <span class="ei-unread" id="eiUnread">0 unread</span>
  </div>
  <ul class="ei-list" id="eiList"></ul>
</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

Unread emphasis
Bolder text, tint, and a dot for unread mail.
Click to read
Opening a row clears its unread state and updates the count.
Star toggles
Per-row stars that persist and re-render in place.
Event isolation
Checkbox and star stop propagation so the row does not open.
Tri-state select-all
Header checkbox checked/unchecked/indeterminate.
Live search + highlight
Filters by sender/subject and marks the match.
Empty state
A friendly message when nothing matches.
No library
Pure HTML/CSS/JS — no mail UI dependency.

About this UI Snippet

Email Inbox — Mail List with Unread, Star, Select-All and Search

Screenshot of the Email Inbox snippet rendered live

An email inbox list is the dense, interaction-rich list at the heart of any mail or messaging app: unread rows stand out, clicking marks read, stars toggle, a header checkbox selects all, and search filters live. This snippet builds that whole interaction model from a data array, in plain HTML, CSS, and vanilla JavaScript.

Unread emphasis and click-to-read

Unread messages get a bolder sender and subject, a subtle tinted background, and a blue dot; clicking a row marks it read and the emphasis drops away. This read/unread state lives on each message object and drives both the styling and the header's "N unread" counter, so they always agree. It's the core inbox behaviour, reproduced faithfully.

Per-row controls without misfires

Each row carries a checkbox and a star, and both stopPropagation so toggling them doesn't also trigger the row's click-to-read. This event-isolation detail is what stops the classic bug where starring an email accidentally opens it. Stars persist on the message object and re-render in place.

Tri-state select-all

The header checkbox is a proper tri-state control: checked when every message is selected, unchecked when none are, and indeterminate (a dash) when only some are — using the real DOM indeterminate property. Selecting individual rows updates it, and toggling it selects or clears everything, the exact behaviour of Gmail-style bulk selection.

Live search with highlighting

Typing in the search box filters the list by sender and subject as you go, and the matching substring in the subject is wrapped in <b> so you can see why a row matched. An empty-state message appears when nothing matches. Filtering is a view concern over the source array, so selection and read state survive a search.

Data-driven and portable

The entire list renders from a DATA array, so swapping in real messages — or a fetch — is a one-line change, and every interaction mutates that data and re-renders, keeping the UI consistent. It's a compact, dependency-free reference for the inbox list pattern you'd otherwise build piecemeal.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSAn inbox renders with unread rows, stars, and a search bar.
  2. 2
    Click a messageIt marks read and the unread count updates.
  3. 3
    Star and selectToggle a star or a checkbox without opening the row.
  4. 4
    Select allThe header checkbox shows a tri-state and bulk-selects.
  5. 5
    SearchFilter by sender or subject with live highlighting.
  6. 6
    Use your dataReplace the DATA array with real messages or a fetch.

Real-world uses

Common Use Cases

Webmail and messaging apps
Provide the core list beside a side drawer, with unread rows shown by bolder text, a tint and a dot until they are opened.
Notification centres
Reuse the read and unread rows for a notification center, with opening a row clearing its unread state and updating the count.
Support ticket queues
Triage conversations with a tri-state select-all header checkbox, with checkbox and star clicks stopping propagation so the row does not open.
Digest and activity lists
Mark items read in a changelog feed style digest, with per-row stars that persist and re-render in place.
Admin record lists
Offer searchable, selectable rows with status styling, where live search highlighting shows exactly why each row matched the query.
Related: Image Hotspot with Tooltips
See the Image Hotspot with Tooltips for a related layouts pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Each message object has an unread flag. Unread rows get bolder text, a tint, and a dot via a class, and clicking a row sets unread to false and re-renders. The header's "N unread" counter is computed from the same data on every render, so the styling and the count can never drift apart.

The row itself has a click handler that marks the message read (and would open it in a real app). Without stopPropagation, clicking the star or checkbox would bubble up and also trigger that handler — so starring an email would open it. Stopping propagation isolates the per-row controls from the row action, which is the correct, expected behaviour.

The header checkbox reflects the selection: checked when all messages are selected, unchecked when none are, and indeterminate (shown as a dash) when only some are — set via the DOM indeterminate property, which cannot be done in HTML. Toggling individual rows recomputes it, and clicking it selects or clears every row.

No. Search only filters which rows are displayed; it does not modify the underlying DATA array or the selection map. So selected and read states persist through a search, and clearing the query restores the full list intact with those states preserved.

Hold the messages and a selection set in state and render rows from them; each interaction updates state and the framework re-renders. Compute the unread count and the select-all tri-state as derived values, setting the indeterminate property via a ref/directive. Filter for search in render. Tailwind users swap the classes for utilities; the data-driven structure is the same.