Empty States — No-search-results state
A live product-search panel that handles the moment a query returns nothing, turning a dead end into a recovery path. As you type, results filter in real time against a small in-memory catalog; when nothing matches, a calm empty state takes over with an animated magnifier illustration, a precise no-results headline echoing the query, spelling and keyword tips, a Clear-search action, and clickable suggested queries that refill the input. A segmented switcher demos three flavors: full suggestions, minimal, and active-filter chips you can clear.
MCP
Code
:root {
--brand: #5b5bf0;
--brand-d: #4646d6;
--brand-700: #3a3ab8;
--brand-50: #eef0ff;
--accent: #00b4a6;
--accent-soft: #d8f5f2;
--ink: #101322;
--ink-2: #3a4060;
--muted: #6c7393;
--bg: #f6f7fb;
--white: #ffffff;
--surface: #ffffff;
--line: rgba(16, 19, 34, 0.1);
--line-2: rgba(16, 19, 34, 0.16);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-1: 0 1px 2px rgba(16, 19, 34, 0.08);
--sh-2: 0 8px 24px rgba(16, 19, 34, 0.08);
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
padding: 40px 20px 64px;
}
.wrap {
max-width: 760px;
margin: 0 auto;
}
/* ---------- Header ---------- */
.page-head {
margin-bottom: 22px;
}
.eyebrow {
display: inline-block;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--brand);
background: var(--brand-50);
padding: 4px 10px;
border-radius: 999px;
}
.page-head h1 {
margin: 12px 0 6px;
font-size: 30px;
font-weight: 800;
letter-spacing: -0.02em;
}
.lede {
margin: 0;
max-width: 56ch;
color: var(--ink-2);
font-size: 15px;
}
/* ---------- Variant switcher ---------- */
.variant-bar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.variant-label {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted);
}
.segmented {
display: inline-flex;
background: var(--white);
border: 1px solid var(--line);
border-radius: 999px;
padding: 4px;
box-shadow: var(--sh-1);
gap: 2px;
}
.seg {
appearance: none;
border: 0;
background: transparent;
font: inherit;
font-size: 13px;
font-weight: 600;
color: var(--ink-2);
padding: 7px 14px;
border-radius: 999px;
cursor: pointer;
transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.seg:hover {
color: var(--ink);
background: var(--brand-50);
}
.seg[aria-checked="true"] {
background: var(--brand);
color: #fff;
box-shadow: 0 2px 8px rgba(91, 91, 240, 0.35);
}
/* ---------- Panel ---------- */
.panel {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-2);
overflow: hidden;
}
.search-head {
padding: 18px 18px 14px;
border-bottom: 1px solid var(--line);
}
.search-field {
position: relative;
display: flex;
align-items: center;
}
.search-ico {
position: absolute;
left: 14px;
width: 19px;
height: 19px;
fill: none;
stroke: var(--muted);
stroke-width: 2;
stroke-linecap: round;
pointer-events: none;
}
.search-input {
flex: 1;
width: 100%;
font: inherit;
font-size: 15px;
color: var(--ink);
background: var(--bg);
border: 1.5px solid var(--line);
border-radius: var(--r-md);
padding: 12px 44px 12px 42px;
transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}
.search-input::placeholder {
color: var(--muted);
}
.search-input:focus {
outline: none;
background: var(--white);
border-color: var(--brand);
box-shadow: 0 0 0 4px rgba(91, 91, 240, 0.15);
}
.search-input::-webkit-search-decoration,
.search-input::-webkit-search-cancel-button {
-webkit-appearance: none;
}
.clear-inline {
position: absolute;
right: 8px;
display: grid;
place-items: center;
width: 30px;
height: 30px;
border: 0;
border-radius: 999px;
background: transparent;
color: var(--muted);
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.clear-inline svg {
width: 16px;
height: 16px;
fill: none;
stroke: currentColor;
stroke-width: 2.2;
stroke-linecap: round;
}
.clear-inline:hover {
background: var(--line);
color: var(--ink);
}
.result-count {
margin: 12px 0 0;
font-size: 13px;
font-weight: 600;
color: var(--muted);
}
/* ---------- Filter bar ---------- */
.filter-bar {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
padding: 12px 18px;
border-bottom: 1px solid var(--line);
background: rgba(91, 91, 240, 0.03);
}
.filter-lead {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--muted);
}
.chip {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 13px;
font-weight: 600;
color: var(--brand-700);
background: var(--brand-50);
border: 1px solid rgba(91, 91, 240, 0.22);
border-radius: 999px;
padding: 5px 6px 5px 12px;
transition: opacity 0.2s, transform 0.2s;
}
.chip-x {
display: grid;
place-items: center;
width: 20px;
height: 20px;
border: 0;
border-radius: 999px;
background: transparent;
color: var(--brand-700);
cursor: pointer;
transition: background 0.15s;
}
.chip-x svg {
width: 12px;
height: 12px;
fill: none;
stroke: currentColor;
stroke-width: 2.4;
stroke-linecap: round;
}
.chip-x:hover {
background: rgba(91, 91, 240, 0.22);
}
.clear-filters {
margin-left: auto;
appearance: none;
border: 0;
background: transparent;
font: inherit;
font-size: 13px;
font-weight: 600;
color: var(--brand);
cursor: pointer;
padding: 5px 6px;
border-radius: var(--r-sm);
}
.clear-filters:hover {
text-decoration: underline;
}
/* ---------- Results ---------- */
.results {
list-style: none;
margin: 0;
padding: 8px;
}
.result-item {
display: flex;
align-items: center;
gap: 14px;
padding: 12px;
border-radius: var(--r-md);
cursor: pointer;
transition: background 0.15s;
animation: itemIn 0.28s ease both;
}
.result-item:hover {
background: var(--bg);
}
.result-item + .result-item {
border-top: 1px solid var(--line);
}
.thumb {
flex: none;
width: 46px;
height: 46px;
border-radius: var(--r-md);
display: grid;
place-items: center;
color: #fff;
box-shadow: var(--sh-1);
}
.thumb svg {
width: 22px;
height: 22px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
.r-body {
flex: 1;
min-width: 0;
}
.r-title {
margin: 0;
font-size: 14.5px;
font-weight: 600;
color: var(--ink);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.r-meta {
margin: 2px 0 0;
font-size: 12.5px;
color: var(--muted);
}
.r-side {
flex: none;
text-align: right;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.r-price {
font-size: 14px;
font-weight: 700;
color: var(--ink);
}
.badge {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.02em;
padding: 2px 8px;
border-radius: 999px;
}
.badge.stock {
color: var(--ok);
background: rgba(47, 158, 111, 0.12);
}
.badge.low {
color: var(--warn);
background: rgba(217, 138, 43, 0.14);
}
@keyframes itemIn {
from {
opacity: 0;
transform: translateY(6px);
}
}
/* ---------- Empty state ---------- */
.empty {
padding: 44px 24px 40px;
text-align: center;
animation: emptyIn 0.32s ease both;
}
@keyframes emptyIn {
from {
opacity: 0;
transform: translateY(10px);
}
}
.empty-art {
width: 96px;
height: 96px;
margin: 0 auto 18px;
display: grid;
place-items: center;
background: var(--brand-50);
border-radius: 50%;
}
.empty-art svg {
width: 64px;
height: 64px;
}
.art-ring {
fill: var(--white);
stroke: var(--brand);
stroke-width: 4;
}
.art-handle {
stroke: var(--brand-700);
stroke-width: 6;
stroke-linecap: round;
}
.art-cross {
stroke: var(--danger);
stroke-width: 3.4;
stroke-linecap: round;
transform-origin: 52px 52px;
}
.art-cross.a {
animation: crossPop 0.4s 0.12s ease both;
}
.art-cross.b {
animation: crossPop 0.4s 0.2s ease both;
}
@keyframes crossPop {
from {
opacity: 0;
transform: scale(0.5);
}
}
.art-spark {
fill: var(--accent);
animation: spark 2.6s ease-in-out infinite;
}
.art-spark.s2 {
animation-delay: 0.5s;
}
.art-spark.s3 {
animation-delay: 1.1s;
}
@keyframes spark {
0%, 100% {
opacity: 0.25;
transform: translateY(0);
}
50% {
opacity: 1;
transform: translateY(-3px);
}
}
.empty-title {
margin: 0 0 6px;
font-size: 20px;
font-weight: 700;
letter-spacing: -0.01em;
}
.empty-q {
color: var(--brand);
word-break: break-word;
}
.empty-sub {
margin: 0 auto 18px;
max-width: 44ch;
color: var(--ink-2);
font-size: 14.5px;
}
.empty-tips {
list-style: none;
margin: 0 auto 22px;
padding: 0;
display: inline-flex;
flex-direction: column;
gap: 8px;
text-align: left;
}
.empty-tips li {
display: flex;
align-items: center;
gap: 9px;
font-size: 13.5px;
color: var(--ink-2);
}
.empty-tips svg {
flex: none;
width: 16px;
height: 16px;
fill: none;
stroke: var(--ok);
stroke-width: 2.4;
stroke-linecap: round;
stroke-linejoin: round;
}
.empty-suggested {
margin: 0 0 24px;
}
.suggested-lead {
margin: 0 0 10px;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--muted);
}
.suggested-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
.s-chip {
appearance: none;
font: inherit;
font-size: 13px;
font-weight: 600;
color: var(--brand-700);
background: var(--white);
border: 1.5px solid var(--line-2);
border-radius: 999px;
padding: 7px 14px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
transition: border-color 0.15s, background 0.15s, transform 0.12s, color 0.15s;
}
.s-chip svg {
width: 13px;
height: 13px;
fill: none;
stroke: var(--brand);
stroke-width: 2;
stroke-linecap: round;
}
.s-chip:hover {
border-color: var(--brand);
background: var(--brand-50);
color: var(--brand-700);
}
.s-chip:active {
transform: scale(0.96);
}
.empty-actions {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
.btn {
appearance: none;
font: inherit;
font-size: 14px;
font-weight: 600;
border-radius: var(--r-md);
padding: 10px 18px;
cursor: pointer;
border: 1.5px solid transparent;
transition: background 0.15s, border-color 0.15s, transform 0.12s, box-shadow 0.15s;
}
.btn:active {
transform: translateY(1px);
}
.btn.primary {
background: var(--brand);
color: #fff;
box-shadow: 0 4px 14px rgba(91, 91, 240, 0.32);
}
.btn.primary:hover {
background: var(--brand-d);
}
.btn.ghost {
background: var(--white);
color: var(--ink-2);
border-color: var(--line-2);
}
.btn.ghost:hover {
background: var(--bg);
color: var(--ink);
border-color: var(--brand);
}
/* Variant: minimal hides suggestions */
.panel[data-variant="minimal"] .empty-suggested,
.panel[data-variant="minimal"] .tip-filters {
display: none;
}
.panel:not([data-variant="filters"]) .filter-bar {
display: none !important;
}
.panel:not([data-variant="filters"]) .tip-filters {
display: none;
}
/* ---------- Footnote ---------- */
.footnote {
margin: 20px 4px 0;
font-size: 12.5px;
color: var(--muted);
font-style: italic;
}
/* ---------- Focus ---------- */
:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
border-radius: var(--r-sm);
}
.search-input:focus-visible {
outline: none;
}
/* ---------- Toast ---------- */
.toast-host {
position: fixed;
left: 50%;
bottom: 24px;
transform: translateX(-50%);
display: flex;
flex-direction: column;
gap: 8px;
z-index: 50;
pointer-events: none;
}
.toast {
display: flex;
align-items: center;
gap: 9px;
background: var(--ink);
color: #fff;
font-size: 13.5px;
font-weight: 500;
padding: 11px 16px;
border-radius: var(--r-md);
box-shadow: var(--sh-2);
animation: toastIn 0.25s ease both;
}
.toast svg {
width: 16px;
height: 16px;
fill: none;
stroke: var(--accent);
stroke-width: 2.4;
stroke-linecap: round;
stroke-linejoin: round;
}
.toast.out {
animation: toastOut 0.25s ease both;
}
@keyframes toastIn {
from {
opacity: 0;
transform: translateY(12px) scale(0.96);
}
}
@keyframes toastOut {
to {
opacity: 0;
transform: translateY(12px) scale(0.96);
}
}
/* ---------- Responsive ---------- */
@media (max-width: 520px) {
body {
padding: 28px 14px 56px;
}
.page-head h1 {
font-size: 24px;
}
.variant-bar {
gap: 8px;
}
.segmented {
width: 100%;
justify-content: space-between;
}
.seg {
flex: 1;
padding: 8px 6px;
font-size: 12px;
text-align: center;
}
.empty {
padding: 34px 16px 32px;
}
.empty-actions {
flex-direction: column;
}
.empty-actions .btn {
width: 100%;
}
.r-title {
font-size: 14px;
}
.clear-filters {
margin-left: 0;
}
}
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.001s !important;
animation-iteration-count: 1 !important;
}
}(function () {
"use strict";
// ---- In-memory dataset (fictional) ----
var PRODUCTS = [
{ id: 1, name: "Aurora Wireless Headphones", cat: "Audio", price: 129, stock: "in", color: "#5b5bf0", icon: "headphones" },
{ id: 2, name: "Pebble Bluetooth Earbuds", cat: "Audio", price: 49, stock: "low", color: "#00b4a6", icon: "earbuds" },
{ id: 3, name: "Volt Portable Speaker", cat: "Audio", price: 79, stock: "in", color: "#d98a2b", icon: "speaker" },
{ id: 4, name: "Lumen Smart Desk Lamp", cat: "Home", price: 64, stock: "in", color: "#d4503e", icon: "lamp" },
{ id: 5, name: "Drift Mechanical Keyboard", cat: "Computing", price: 119, stock: "low", color: "#3a3ab8", icon: "keyboard" },
{ id: 6, name: "Quartz Wireless Mouse", cat: "Computing", price: 39, stock: "in", color: "#2f9e6f", icon: "mouse" },
{ id: 7, name: "Nimbus Standing Mug Warmer", cat: "Home", price: 29, stock: "in", color: "#4646d6", icon: "mug" },
{ id: 8, name: "Tidal Noise-Cancelling Buds", cat: "Audio", price: 99, stock: "in", color: "#00b4a6", icon: "earbuds" }
];
var SUGGESTIONS = ["headphones", "earbuds", "keyboard", "desk lamp", "speaker"];
var ICONS = {
headphones: '<path d="M4 13v-1a8 8 0 0 1 16 0v1"/><rect x="3" y="13" width="4" height="7" rx="1.5"/><rect x="17" y="13" width="4" height="7" rx="1.5"/>',
earbuds: '<circle cx="8" cy="8" r="3.5"/><path d="M8 11v6"/><circle cx="16" cy="16" r="3.5"/><path d="M16 13V7"/>',
speaker: '<rect x="6" y="3" width="12" height="18" rx="2.5"/><circle cx="12" cy="15" r="3.5"/><circle cx="12" cy="7" r="1"/>',
lamp: '<path d="M9 18h6l-1 3H10z"/><path d="M12 18v-4"/><path d="M7 9l5-6 5 6z"/>',
keyboard: '<rect x="3" y="7" width="18" height="11" rx="2"/><path d="M7 11h0M11 11h0M15 11h0M8 15h8"/>',
mouse: '<rect x="7" y="3" width="10" height="18" rx="5"/><path d="M12 7v3"/>',
mug: '<path d="M5 8h11v7a4 4 0 0 1-4 4H9a4 4 0 0 1-4-4z"/><path d="M16 10h2a2.5 2.5 0 0 1 0 5h-2"/>'
};
// ---- Elements ----
var panel = document.querySelector(".panel");
var input = document.getElementById("q");
var clearInline = document.getElementById("clearInline");
var results = document.getElementById("results");
var count = document.getElementById("count");
var empty = document.getElementById("empty");
var emptyQ = document.getElementById("emptyQ");
var suggested = document.getElementById("suggested");
var clearSearch = document.getElementById("clearSearch");
var browseAll = document.getElementById("browseAll");
var filterBar = document.getElementById("filterBar");
var clearFilters = document.getElementById("clearFilters");
var toastHost = document.getElementById("toastHost");
var segButtons = Array.prototype.slice.call(document.querySelectorAll(".seg"));
var variant = "suggested";
// active filters narrow the dataset; in the "filters" variant they're on by default
var filters = { cat: false, price: false, stock: false };
// ---- Toast ----
var toastTimer;
function toast(msg) {
var el = document.createElement("div");
el.className = "toast";
el.innerHTML =
'<svg viewBox="0 0 24 24" aria-hidden="true"><polyline points="5 13 10 18 19 6"/></svg><span></span>';
el.querySelector("span").textContent = msg;
toastHost.appendChild(el);
setTimeout(function () {
el.classList.add("out");
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 240);
}, 2200);
}
// ---- Filtering logic ----
function matches(p, q) {
var hay = (p.name + " " + p.cat).toLowerCase();
return hay.indexOf(q) !== -1;
}
function passesFilters(p) {
if (filters.cat && p.cat !== "Audio") return false;
if (filters.price && p.price >= 50) return false;
if (filters.stock && p.stock !== "in") return false;
return true;
}
function compute() {
var q = input.value.trim().toLowerCase();
var list = PRODUCTS.filter(function (p) {
return (q === "" || matches(p, q)) && passesFilters(p);
});
return list;
}
// ---- Render ----
function renderItem(p) {
var stockBadge =
p.stock === "low"
? '<span class="badge low">Low stock</span>'
: '<span class="badge stock">In stock</span>';
var li = document.createElement("li");
li.className = "result-item";
li.tabIndex = 0;
li.setAttribute("role", "button");
li.innerHTML =
'<span class="thumb" style="background:' +
p.color +
'"><svg viewBox="0 0 24 24" aria-hidden="true">' +
ICONS[p.icon] +
"</svg></span>" +
'<div class="r-body">' +
'<p class="r-title">' +
esc(p.name) +
"</p>" +
'<p class="r-meta">' +
esc(p.cat) +
" · Free shipping</p>" +
"</div>" +
'<div class="r-side"><span class="r-price">$' +
p.price +
"</span>" +
stockBadge +
"</div>";
li.addEventListener("click", function () {
toast("Opened “" + p.name + "”");
});
li.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toast("Opened “" + p.name + "”");
}
});
return li;
}
function esc(s) {
return String(s).replace(/[&<>"]/g, function (c) {
return { "&": "&", "<": "<", ">": ">", '"': """ }[c];
});
}
function renderSuggested() {
suggested.innerHTML = "";
SUGGESTIONS.forEach(function (s) {
var b = document.createElement("button");
b.type = "button";
b.className = "s-chip";
b.innerHTML =
'<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><line x1="16" y1="16" x2="21" y2="21"/></svg>' +
'<span>' + esc(s) + "</span>";
b.addEventListener("click", function () {
// clear filters so the suggested query actually returns something
setAllFilters(false);
input.value = s;
input.focus();
render();
toast("Searching for “" + s + "”");
});
suggested.appendChild(b);
});
}
function render() {
var q = input.value.trim();
clearInline.hidden = q.length === 0;
var list = compute();
if (list.length > 0) {
empty.hidden = true;
results.hidden = false;
results.innerHTML = "";
list.forEach(function (p, i) {
var node = renderItem(p);
node.style.animationDelay = i * 0.03 + "s";
results.appendChild(node);
});
count.textContent = list.length + (list.length === 1 ? " result" : " results");
} else {
results.innerHTML = "";
results.hidden = true;
emptyQ.textContent = "“" + (q || "your search") + "”";
empty.hidden = false;
count.textContent = "0 results";
}
}
// ---- Filters ----
function syncFilterBar() {
var any = filters.cat || filters.price || filters.stock;
// bar visibility for the filters variant is also controlled by CSS;
// here we toggle individual chips
filterBar.hidden = variant !== "filters";
if (variant === "filters") {
document.querySelectorAll(".chip").forEach(function (chip) {
var key = chip.getAttribute("data-filter");
chip.style.display = filters[key] ? "" : "none";
});
clearFilters.style.display = any ? "" : "none";
}
}
function setAllFilters(on) {
filters.cat = on;
filters.price = on;
filters.stock = on;
syncFilterBar();
}
document.querySelectorAll(".chip-x").forEach(function (btn) {
btn.addEventListener("click", function () {
var key = btn.getAttribute("data-remove");
filters[key] = false;
syncFilterBar();
render();
toast("Filter removed");
});
});
clearFilters.addEventListener("click", function () {
setAllFilters(false);
render();
toast("All filters cleared");
});
// ---- Variant switcher ----
function setVariant(v) {
variant = v;
panel.setAttribute("data-variant", v);
segButtons.forEach(function (b) {
var on = b.getAttribute("data-variant") === v;
b.setAttribute("aria-checked", on ? "true" : "false");
});
if (v === "filters") {
setAllFilters(true);
// seed a query that has matches under the audio/under-$50/in-stock filters
input.value = "buds";
} else if (v === "minimal") {
setAllFilters(false);
input.value = "zxcv";
} else {
setAllFilters(false);
input.value = "wireless headphones";
}
syncFilterBar();
render();
}
segButtons.forEach(function (b, idx) {
b.addEventListener("click", function () {
setVariant(b.getAttribute("data-variant"));
});
b.addEventListener("keydown", function (e) {
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
e.preventDefault();
var next = segButtons[(idx + 1) % segButtons.length];
next.focus();
next.click();
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
e.preventDefault();
var prev = segButtons[(idx - 1 + segButtons.length) % segButtons.length];
prev.focus();
prev.click();
}
});
});
// ---- Search input ----
input.addEventListener("input", render);
input.addEventListener("keydown", function (e) {
if (e.key === "Escape" && input.value) {
input.value = "";
render();
}
});
clearInline.addEventListener("click", function () {
input.value = "";
input.focus();
render();
});
clearSearch.addEventListener("click", function () {
input.value = "";
setAllFilters(false);
input.focus();
render();
toast("Search cleared");
});
browseAll.addEventListener("click", function () {
input.value = "";
setAllFilters(false);
render();
toast("Showing all products");
});
// ---- Init ----
renderSuggested();
setVariant("suggested");
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Empty States — No-search-results state</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="wrap">
<header class="page-head">
<span class="eyebrow">Empty states</span>
<h1>No-search-results state</h1>
<p class="lede">A live product search that gracefully handles the moment a query matches nothing — with a clear empty state, recovery actions, and suggested searches.</p>
</header>
<div class="variant-bar" role="group" aria-label="Demo variant">
<span class="variant-label">Variant</span>
<div class="segmented" role="radiogroup" aria-label="Empty-state variant">
<button type="button" class="seg" role="radio" aria-checked="true" data-variant="suggested">With suggestions</button>
<button type="button" class="seg" role="radio" aria-checked="false" data-variant="minimal">Minimal</button>
<button type="button" class="seg" role="radio" aria-checked="false" data-variant="filters">Active filters</button>
</div>
</div>
<section class="panel" aria-label="Search results">
<div class="search-head">
<div class="search-field">
<svg class="search-ico" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="11" cy="11" r="7" />
<line x1="16.5" y1="16.5" x2="21" y2="21" />
</svg>
<input
id="q"
type="search"
class="search-input"
placeholder="Search products, e.g. “wireless headphones”"
aria-label="Search products"
autocomplete="off"
spellcheck="false"
value="wireless headphones"
/>
<button type="button" id="clearInline" class="clear-inline" aria-label="Clear search" hidden>
<svg viewBox="0 0 24 24" aria-hidden="true"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>
</button>
</div>
<p class="result-count" id="count" aria-live="polite" role="status">3 results</p>
</div>
<div class="filter-bar" id="filterBar" hidden>
<span class="filter-lead">Filters</span>
<span class="chip" data-filter="cat">
Category: Audio
<button type="button" class="chip-x" aria-label="Remove Audio filter" data-remove="cat"><svg viewBox="0 0 24 24" aria-hidden="true"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg></button>
</span>
<span class="chip" data-filter="price">
Under $50
<button type="button" class="chip-x" aria-label="Remove price filter" data-remove="price"><svg viewBox="0 0 24 24" aria-hidden="true"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg></button>
</span>
<span class="chip" data-filter="stock">
In stock
<button type="button" class="chip-x" aria-label="Remove in-stock filter" data-remove="stock"><svg viewBox="0 0 24 24" aria-hidden="true"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg></button>
</span>
<button type="button" class="clear-filters" id="clearFilters">Clear all filters</button>
</div>
<ul class="results" id="results" aria-label="Matching products"></ul>
<div class="empty" id="empty" hidden role="status">
<div class="empty-art" aria-hidden="true">
<svg viewBox="0 0 120 120">
<circle class="art-ring" cx="52" cy="52" r="30" />
<line class="art-handle" x1="74" y1="74" x2="98" y2="98" />
<line class="art-cross a" x1="42" y1="42" x2="62" y2="62" />
<line class="art-cross b" x1="62" y1="42" x2="42" y2="62" />
<circle class="art-spark s1" cx="96" cy="30" r="2.5" />
<circle class="art-spark s2" cx="22" cy="86" r="2" />
<circle class="art-spark s3" cx="104" cy="60" r="1.6" />
</svg>
</div>
<h2 class="empty-title">No results for <span class="empty-q" id="emptyQ">“…”</span></h2>
<p class="empty-sub">We couldn’t find anything matching your search. Try one of these:</p>
<ul class="empty-tips">
<li><svg viewBox="0 0 24 24" aria-hidden="true"><polyline points="5 13 10 18 19 6"/></svg> Check your spelling</li>
<li><svg viewBox="0 0 24 24" aria-hidden="true"><polyline points="5 13 10 18 19 6"/></svg> Use fewer or more general keywords</li>
<li class="tip-filters" id="tipFilters"><svg viewBox="0 0 24 24" aria-hidden="true"><polyline points="5 13 10 18 19 6"/></svg> Remove active filters to widen results</li>
</ul>
<div class="empty-suggested" id="suggestedWrap">
<p class="suggested-lead">Try searching for</p>
<div class="suggested-chips" id="suggested"></div>
</div>
<div class="empty-actions">
<button type="button" class="btn primary" id="clearSearch">Clear search</button>
<button type="button" class="btn ghost" id="browseAll">Browse all products</button>
</div>
</div>
</section>
<p class="footnote">Illustrative UI only — fictional products and data.</p>
</main>
<div class="toast-host" id="toastHost" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>No-search-results state
A self-contained search results panel for a fictional gadget store. A live search field filters a small in-memory catalog as you type, rendering matching products as rows with color-coded thumbnails, category, price, and an in-stock or low-stock badge. A polite aria-live counter reports how many results are showing, and an inline clear button appears as soon as the field has text.
When a query matches nothing, the panel swaps the list for a no-results empty state: a CSS-animated magnifier illustration with a red “no match” cross, a headline that echoes the exact query — No results for ”…” — and a short list of recovery tips (check spelling, broaden keywords, remove filters). Below that, a row of suggested-query chips refills the input and re-runs the search on click, plus Clear search and Browse all products actions to get unstuck. Every action confirms with a small toast.
A segmented switcher renders three live variants: With suggestions (the full recovery state), Minimal (illustration, headline, and a single clear action — no suggested chips), and Active filters (filter chips for Category, Price, and In-stock that narrow results and can be removed individually or all at once, with a matching “remove filters” tip). The switcher is keyboard-navigable with arrow keys, Esc clears the field, and the layout collapses to a single column with stacked actions down to 360px.
Illustrative UI only — fictional products and data.