Bootstrap 5 Message Inbox with Chat Layout
A two-pane messaging layout with a filterable, searchable conversation list and a chat view where you can send messages and get replies.
<section class="wdw-stage">
<div class="container" style="max-width:1100px">
<div class="wdw-card overflow-hidden mail" id="mail">
<div class="row g-0 mail-grid">
<aside class="col-md-5 col-lg-4 border-end mail-list d-flex flex-column" aria-label="Conversations">
<div class="p-3 pb-2 d-flex align-items-center justify-content-between">
<h1 class="h5 fw-bold mb-0">Messages</h1>
<button class="btn btn-primary btn-sm rounded-pill px-3" type="button">New</button>
</div>
<ul class="nav nav-pills px-3 gap-1 mb-2 flex-nowrap overflow-auto" id="mailTabs">
<li class="nav-item"><button class="nav-link active py-1 px-3 rounded-pill d-flex align-items-center gap-1" data-filter="all" type="button" aria-pressed="true"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.5 5.1 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.5-6.9A2 2 0 0 0 16.7 4H7.2a2 2 0 0 0-1.7 1.1Z"/></svg>Inbox <span class="badge text-bg-primary-subtle ms-1">4</span></button></li>
<li class="nav-item"><button class="nav-link py-1 px-3 rounded-pill d-flex align-items-center gap-1" data-filter="starred" type="button" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1Z"/></svg>Starred <span class="badge text-bg-primary-subtle ms-1">2</span></button></li>
<li class="nav-item"><button class="nav-link py-1 px-3 rounded-pill d-flex align-items-center gap-1" data-filter="sent" type="button" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 2 11 13M22 2l-7 20-4-9-9-4 20-7Z"/></svg>Sent <span class="badge text-bg-primary-subtle ms-1">2</span></button></li>
</ul>
<div class="px-3 pb-2">
<label class="visually-hidden" for="mailSearch">Search messages</label>
<input id="mailSearch" type="search" class="form-control form-control-sm rounded-pill bg-body-tertiary border-0 px-3" placeholder="Search messages">
</div>
<div class="list-group list-group-flush overflow-auto flex-grow-1" id="mailItems">
<button class="list-group-item list-group-item-action active-thread d-flex gap-3 border-0" type="button" data-thread="0" data-tags="all" aria-current="true">
<img src="https://i.pravatar.cc/80?img=12" class="rounded-circle" width="44" height="44" alt=""><span class="flex-grow-1 text-start min-w-0"><span class="d-flex justify-content-between"><strong class="small">Elon Musk</strong><span class="small wdw-muted">2:32 PM</span></span><span class="small wdw-muted d-block text-truncate">I've seen your new template, it's amazing!</span></span><span class="badge rounded-pill text-bg-primary align-self-center" data-unread>1<span class="visually-hidden"> unread message</span></span>
</button>
<button class="list-group-item list-group-item-action d-flex gap-3 border-0" type="button" data-thread="1" data-tags="all starred">
<img src="https://i.pravatar.cc/80?img=32" class="rounded-circle" width="44" height="44" alt=""><span class="flex-grow-1 text-start min-w-0"><span class="d-flex justify-content-between"><strong class="small">Sara Chen</strong><span class="small wdw-muted">3:56 PM</span></span><span class="small wdw-muted d-block text-truncate">Of course, just call me before that.</span></span>
</button>
<button class="list-group-item list-group-item-action d-flex gap-3 border-0" type="button" data-thread="2" data-tags="all sent">
<img src="https://i.pravatar.cc/80?img=47" class="rounded-circle" width="44" height="44" alt=""><span class="flex-grow-1 text-start min-w-0"><span class="d-flex justify-content-between"><strong class="small">Maya Ortiz</strong><span class="small wdw-muted">5:21 PM</span></span><span class="small wdw-muted d-block text-truncate">Hey, you asked for my feedback. It's brilliant.</span></span><span class="badge rounded-pill text-bg-primary align-self-center" data-unread>1<span class="visually-hidden"> unread message</span></span>
</button>
<button class="list-group-item list-group-item-action d-flex gap-3 border-0" type="button" data-thread="3" data-tags="all starred sent">
<img src="https://i.pravatar.cc/80?img=5" class="rounded-circle" width="44" height="44" alt=""><span class="flex-grow-1 text-start min-w-0"><span class="d-flex justify-content-between"><strong class="small">Priya Nair</strong><span class="small wdw-muted">6:13 PM</span></span><span class="small wdw-muted d-block text-truncate">And invite Daniel too, please.</span></span>
</button>
</div>
</aside>
<div class="col-md-7 col-lg-8 d-flex flex-column mail-chat bg-body-tertiary">
<header class="d-flex align-items-center gap-3 p-3 bg-body border-bottom shadow-sm position-relative">
<button class="btn btn-sm btn-outline-secondary rounded-circle p-0 d-md-none flex-shrink-0" style="width:44px;height:44px" type="button" id="mailBack" aria-label="Back to conversations"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg></button>
<img id="chatAvatar" src="https://i.pravatar.cc/80?img=12" class="rounded-circle" width="40" height="40" alt="">
<div><div class="fw-bold" id="chatName">Elon Musk</div><div class="small text-success">Online</div></div>
</header>
<div class="flex-grow-1 p-3 p-md-4 d-flex flex-column gap-3 overflow-auto" id="chatLog" role="log" aria-live="polite" aria-label="Conversation"></div>
<form class="p-3 bg-body border-top d-flex gap-2" id="chatForm">
<label class="visually-hidden" for="chatInput">Message</label>
<input id="chatInput" class="form-control rounded-pill px-4" placeholder="Write a message" autocomplete="off">
<button class="btn btn-primary rounded-circle p-0 flex-shrink-0" style="width:44px" type="submit" aria-label="Send"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 2 11 13M22 2l-7 20-4-9-9-4 20-7Z"/></svg></button>
</form>
</div>
</div>
</div>
</div>
</section>
/* Web Designer Wall library style — shared base for every Bootstrap 5 snippet.
Indigo accent, Plus Jakarta Sans, soft cards, pill buttons. Works in light and dark. */
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap");
:root,
[data-bs-theme="light"] {
--wdw-font: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--bs-body-font-family: var(--wdw-font);
--bs-primary: #4f46e5;
--bs-primary-rgb: 79, 70, 229;
--bs-primary-text-emphasis: #3730a3;
--bs-primary-bg-subtle: #eef2ff;
--bs-primary-border-subtle: #c7d2fe;
--bs-link-color: #4f46e5;
--bs-link-color-rgb: 79, 70, 229;
--bs-link-hover-color: #4338ca;
--bs-link-hover-color-rgb: 67, 56, 202;
--bs-focus-ring-color: rgba(79, 70, 229, 0.25);
--bs-border-radius: 0.75rem;
--bs-border-radius-lg: 1rem;
--bs-border-radius-xl: 1.25rem;
--wdw-radius-card: 1.25rem;
--wdw-shadow: 0 1px 2px rgba(16, 24, 40, 0.05), 0 16px 40px -16px rgba(16, 24, 40, 0.22);
--wdw-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06), 0 4px 12px -4px rgba(16, 24, 40, 0.12);
--wdw-stage: #f5f6fb;
}
[data-bs-theme="dark"] {
--bs-primary: #818cf8;
--bs-primary-rgb: 129, 140, 248;
--bs-primary-text-emphasis: #c7d2fe;
--bs-primary-bg-subtle: rgba(129, 140, 248, 0.14);
--bs-primary-border-subtle: rgba(129, 140, 248, 0.35);
--bs-link-color: #a5b4fc;
--bs-link-color-rgb: 165, 180, 252;
--bs-link-hover-color: #c7d2fe;
--bs-link-hover-color-rgb: 199, 210, 254;
--bs-focus-ring-color: rgba(129, 140, 248, 0.35);
--wdw-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 16px 40px -16px rgba(0, 0, 0, 0.7);
--wdw-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px -4px rgba(0, 0, 0, 0.5);
--wdw-stage: #0f1117;
}
body { font-family: var(--wdw-font); -webkit-font-smoothing: antialiased; }
/* Bootstrap compiles its blue into these components; point them at the accent. */
.btn-primary {
--bs-btn-bg: var(--bs-primary); --bs-btn-border-color: var(--bs-primary);
--bs-btn-hover-bg: #4338ca; --bs-btn-hover-border-color: #4338ca;
--bs-btn-active-bg: #3730a3; --bs-btn-active-border-color: #3730a3;
--bs-btn-disabled-bg: var(--bs-primary); --bs-btn-disabled-border-color: var(--bs-primary);
--bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
}
[data-bs-theme="dark"] .btn-primary { --bs-btn-color: #0f1117; --bs-btn-hover-bg: #a5b4fc; --bs-btn-hover-border-color: #a5b4fc; --bs-btn-hover-color: #0f1117; --bs-btn-active-color: #0f1117; }
.btn-outline-primary {
--bs-btn-color: var(--bs-primary); --bs-btn-border-color: var(--bs-primary);
--bs-btn-hover-bg: var(--bs-primary); --bs-btn-hover-border-color: var(--bs-primary);
--bs-btn-active-bg: var(--bs-primary); --bs-btn-active-border-color: var(--bs-primary);
--bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
}
.btn { --bs-btn-font-weight: 600; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; gap: .5rem; }
.btn-sm { min-height: 36px; }
.form-control, .form-select { min-height: 44px; border-radius: .75rem; }
.form-control:focus, .form-select:focus, .form-check-input:focus { border-color: rgba(var(--bs-primary-rgb), .6); box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
.form-check-input:checked { background-color: var(--bs-primary); border-color: var(--bs-primary); }
.nav-pills { --bs-nav-pills-link-active-bg: var(--bs-primary); }
.nav { --bs-nav-link-color: var(--bs-secondary-color); --bs-nav-link-hover-color: var(--bs-emphasis-color); }
.pagination { --bs-pagination-active-bg: var(--bs-primary); --bs-pagination-active-border-color: var(--bs-primary); --bs-pagination-color: var(--bs-body-color); --bs-pagination-focus-box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
.progress, .progress-stacked { --bs-progress-bar-bg: var(--bs-primary); }
.list-group { --bs-list-group-active-bg: var(--bs-primary); --bs-list-group-active-border-color: var(--bs-primary); }
.dropdown-menu { --bs-dropdown-link-active-bg: var(--bs-primary); border-radius: .875rem; box-shadow: var(--wdw-shadow-sm); }
.accordion { --bs-accordion-active-bg: var(--bs-primary-bg-subtle); --bs-accordion-active-color: var(--bs-primary-text-emphasis); --bs-accordion-btn-focus-box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
.badge { font-weight: 600; }
/* Library building blocks — use these instead of re-inventing them per snippet. */
.wdw-stage { min-height: 100vh; display: flex; align-items: center; background: var(--wdw-stage); padding-block: 3rem; }
.wdw-card { background: var(--bs-body-bg); border: 1px solid var(--bs-border-color-translucent); border-radius: var(--wdw-radius-card); box-shadow: var(--wdw-shadow); }
.wdw-card-sm { background: var(--bs-body-bg); border: 1px solid var(--bs-border-color-translucent); border-radius: 1rem; box-shadow: var(--wdw-shadow-sm); }
.wdw-aurora { position: relative; color: #fff; background: radial-gradient(60% 55% at 100% 0, #818cf8 0, transparent 70%), radial-gradient(65% 60% at 0 100%, #ec4899 0, transparent 65%), #1e1b4b; }
.wdw-eyebrow { display: inline-block; font-size: .75rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--bs-primary); }
.wdw-heading { font-weight: 800; letter-spacing: -.02em; color: var(--bs-emphasis-color); }
.wdw-muted { color: var(--bs-secondary-color); }
.wdw-icon { width: 2.75rem; height: 2.75rem; flex: none; display: inline-flex; align-items: center; justify-content: center; border-radius: .875rem; background: var(--bs-primary-bg-subtle); color: var(--bs-primary); }
/* Component */
.mail-grid { height: 600px; }
.mail-list, .mail-chat { min-height: 0; max-height: 100%; }
.mail .list-group-item { padding: .85rem 1rem; background: transparent; min-height: 44px; }
.mail .list-group-item.active-thread { background: var(--bs-primary-bg-subtle); box-shadow: inset 3px 0 0 var(--bs-primary); }
.min-w-0 { min-width: 0; }
.mail .badge.text-bg-primary-subtle { background: var(--bs-primary-bg-subtle); color: var(--bs-primary-text-emphasis); }
.mail .nav-link { white-space: nowrap; font-weight: 600; min-height: 36px; }
.mail .nav-link.active .badge { background: rgba(255,255,255,.25) !important; color: #fff; }
[data-bs-theme="dark"] .mail .nav-link.active .badge { color: #0f1117; }
.msg { display: flex; flex-direction: column; max-width: 78%; gap: .25rem; }
.msg.in { align-self: flex-start; }
.msg.out { align-self: flex-end; align-items: flex-end; }
.msg-time { font-size: .72rem; color: var(--bs-secondary-color); padding-inline: .35rem; }
.bubble { padding: .7rem 1rem; border-radius: 1.1rem; font-size: .95rem; line-height: 1.45; }
.msg.in .bubble { background: var(--bs-body-bg); border: 1px solid var(--bs-border-color-translucent); border-bottom-left-radius: .3rem; }
.msg.out .bubble { background: var(--bs-primary); color: #fff; border-bottom-right-radius: .3rem; }
[data-bs-theme="dark"] .msg.out .bubble { color: #0f1117; }
.typing { display: inline-flex; gap: .3rem; align-items: center; padding: .85rem 1rem; }
.typing i { width: .45rem; height: .45rem; border-radius: 50%; background: var(--bs-secondary-color); animation: wdw-typing 1s infinite ease-in-out; }
.typing i:nth-child(2) { animation-delay: .15s; } .typing i:nth-child(3) { animation-delay: .3s; }
@keyframes wdw-typing { 0%, 80%, 100% { opacity: .3; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-3px); } }
@media (prefers-reduced-motion: reduce) { .typing i { animation: none; } }
@media (max-width: 767.98px) {
.mail-grid { height: 560px; }
.mail:not(.show-chat) .mail-chat { display: none !important; }
.mail.show-chat .mail-list { display: none !important; }
.mail-list { border-right: 0 !important; }
.msg { max-width: 88%; }
}
(function () {
var threads = [
{ msgs: [['in', "I've seen your new template, it's amazing!", '2:30 PM'], ['out', 'Thanks! Want me to send over the full source files?', '2:31 PM'], ['in', 'Yes please, and the docs if you have them.', '2:32 PM']] },
{ msgs: [['out', 'Can we move the review to Thursday?', '3:48 PM'], ['in', 'Of course, just call me before that.', '3:56 PM']] },
{ msgs: [['out', 'Could you look over the new dashboard layout?', '5:02 PM'], ['in', "Hey, you asked for my feedback. It's brilliant.", '5:21 PM']] },
{ msgs: [['in', 'Booking the kickoff for Monday morning.', '6:05 PM'], ['out', 'Perfect, I will prepare the agenda.', '6:09 PM'], ['in', 'And invite Daniel too, please.', '6:13 PM']] }
];
var replies = ['Sounds good, thank you!', 'Great, talk soon.', 'Perfect, that works for me.'];
var current = 0, timer = null;
function now() { return new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); }
function bubble(dir, text, time) {
var d = document.createElement('div'); d.className = 'msg ' + dir;
var b = document.createElement('div'); b.className = 'bubble'; b.textContent = text;
var t = document.createElement('span'); t.className = 'msg-time'; t.textContent = time;
d.appendChild(b); d.appendChild(t); return d;
}
function render() {
var log = document.getElementById('chatLog');
if (!log) return;
log.textContent = '';
threads[current].msgs.forEach(function (m) { log.appendChild(bubble(m[0], m[1], m[2])); });
log.scrollTop = log.scrollHeight;
}
function initThreads() {
var items = document.querySelectorAll('#mailItems [data-thread]');
if (!items.length) return;
var mail = document.getElementById('mail');
var name = document.getElementById('chatName');
var avatar = document.getElementById('chatAvatar');
var back = document.getElementById('mailBack');
items.forEach(function (it) {
it.addEventListener('click', function () {
items.forEach(function (o) { o.classList.remove('active-thread'); o.removeAttribute('aria-current'); });
it.classList.add('active-thread'); it.setAttribute('aria-current', 'true');
current = +it.dataset.thread;
clearTimeout(timer);
name.textContent = it.querySelector('strong').textContent;
avatar.src = it.querySelector('img').src;
var b = it.querySelector('[data-unread]'); if (b) b.remove();
render();
mail.classList.add('show-chat');
});
});
if (back) back.addEventListener('click', function () { mail.classList.remove('show-chat'); });
}
function initFilters() {
var tabs = document.querySelectorAll('#mailTabs [data-filter]');
var search = document.getElementById('mailSearch');
var items = document.querySelectorAll('#mailItems [data-thread]');
if (!tabs.length) return;
var filter = 'all';
function apply() {
var q = search.value.trim().toLowerCase();
items.forEach(function (it) {
var ok = it.dataset.tags.split(' ').indexOf(filter) > -1 && it.textContent.toLowerCase().indexOf(q) > -1;
it.classList.toggle('d-none', !ok);
});
}
tabs.forEach(function (t) { t.addEventListener('click', function () {
tabs.forEach(function (o) { o.classList.remove('active'); o.setAttribute('aria-pressed', 'false'); });
t.classList.add('active'); t.setAttribute('aria-pressed', 'true'); filter = t.dataset.filter; apply();
}); });
search.addEventListener('input', apply);
}
function initChat() {
var form = document.getElementById('chatForm');
if (!form) return;
var input = document.getElementById('chatInput');
var log = document.getElementById('chatLog');
form.addEventListener('submit', function (e) {
e.preventDefault();
var v = input.value.trim(); if (!v) return;
var idx = current;
threads[idx].msgs.push(['out', v, now()]);
log.appendChild(bubble('out', v, now())); input.value = '';
var typing = document.createElement('div'); typing.className = 'msg in';
typing.innerHTML = '<div class="bubble typing" aria-label="Typing"><i></i><i></i><i></i></div>';
log.appendChild(typing); log.scrollTop = log.scrollHeight;
clearTimeout(timer);
timer = setTimeout(function () {
var r = replies[Math.floor(Math.random() * replies.length)];
threads[idx].msgs.push(['in', r, now()]);
if (idx === current) render();
}, 1400);
});
}
render(); initThreads(); initFilters(); initChat();
})();
Built on Bootstrap 5.3.8 with plain JavaScript — no jQuery. Supports light and dark mode via data-bs-theme.