Empty States — First-use empty state
A polished first-use empty state for a Projects list, built to turn a blank screen into an inviting first step. It centers an inline SVG illustration under the headline No projects yet, supportive subtext, a primary Create your first project button and a secondary Import link. The primary action reveals an inline create form with a name field and accent swatches, then drops a freshly built project card onto the page with a confirming toast. A segmented switcher previews the illustration, icon and minimal styles in both inline-card and full-page layouts.
MCP
Код
: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-text-size-adjust: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, p { margin: 0; }
button { font-family: inherit; }
:focus-visible {
outline: 2px solid var(--brand);
outline-offset: 2px;
border-radius: var(--r-sm);
}
.page {
max-width: 960px;
margin: 0 auto;
padding: 28px 22px 64px;
}
/* ---------- Topbar ---------- */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 10px 14px;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-1);
}
.brand { display: flex; align-items: center; gap: 10px; }
.brand-mark {
display: grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: 10px;
color: var(--brand);
background: var(--brand-50);
}
.brand-name { font-weight: 800; letter-spacing: -0.01em; }
.badge {
font-size: 11px;
font-weight: 700;
padding: 2px 8px;
border-radius: 999px;
letter-spacing: 0.02em;
}
.badge-plan { color: var(--brand-700); background: var(--brand-50); }
.topnav { display: flex; align-items: center; gap: 4px; }
.nav-link {
border: 0;
background: transparent;
color: var(--muted);
font-size: 14px;
font-weight: 600;
padding: 7px 12px;
border-radius: var(--r-sm);
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.nav-link:hover { background: var(--bg); color: var(--ink-2); }
.nav-link.is-active { color: var(--ink); background: var(--brand-50); }
.avatar {
display: grid;
place-items: center;
width: 32px;
height: 32px;
margin-left: 6px;
border-radius: 50%;
font-size: 12px;
font-weight: 700;
color: var(--white);
background: linear-gradient(135deg, var(--brand), var(--accent));
}
/* ---------- Toolbar ---------- */
.toolbar {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 18px;
flex-wrap: wrap;
margin: 26px 2px 16px;
}
.toolbar-titles h1 { font-size: 26px; font-weight: 800; letter-spacing: -0.02em; }
.toolbar-sub { color: var(--muted); font-size: 14px; margin-top: 3px; }
.variant-switch { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.variant-label {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
}
.seg {
display: inline-flex;
padding: 3px;
gap: 2px;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--sh-1);
}
.seg-btn {
border: 0;
background: transparent;
color: var(--muted);
font-size: 13px;
font-weight: 600;
padding: 6px 12px;
border-radius: 9px;
cursor: pointer;
transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.seg-btn:hover { color: var(--ink-2); }
.seg-btn.is-active {
color: var(--brand-700);
background: var(--brand-50);
box-shadow: inset 0 0 0 1px rgba(91, 91, 240, 0.18);
}
/* ---------- Stage ---------- */
.stage {
display: grid;
gap: 16px;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-2);
}
/* ---------- Empty state ---------- */
.empty {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 40px 28px 44px;
position: relative;
overflow: hidden;
}
.empty::before {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(420px 200px at 50% -40px, rgba(91, 91, 240, 0.07), transparent 70%);
pointer-events: none;
}
.empty-art {
position: relative;
z-index: 1;
display: grid;
place-items: center;
margin-bottom: 22px;
}
.art-illustration { display: block; }
.art-icon {
display: none;
place-items: center;
width: 76px;
height: 76px;
border-radius: 20px;
color: var(--brand);
background: var(--brand-50);
box-shadow: inset 0 0 0 1px rgba(91, 91, 240, 0.2);
}
.empty-title {
position: relative;
z-index: 1;
font-size: 21px;
font-weight: 800;
letter-spacing: -0.015em;
}
.empty-sub {
position: relative;
z-index: 1;
max-width: 380px;
margin-top: 8px;
color: var(--muted);
font-size: 14.5px;
}
.empty-actions {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
justify-content: center;
margin-top: 24px;
}
.empty-hints {
position: relative;
z-index: 1;
display: flex;
gap: 18px;
flex-wrap: wrap;
justify-content: center;
list-style: none;
margin: 26px 0 0;
padding: 0;
font-size: 12.5px;
font-weight: 600;
color: var(--ink-2);
}
.empty-hints li { display: inline-flex; align-items: center; gap: 7px; }
.dot { width: 8px; height: 8px; border-radius: 50%; }
.dot-brand { background: var(--brand); }
.dot-accent { background: var(--accent); }
.dot-warn { background: var(--warn); }
/* ---------- Buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
border: 1px solid transparent;
font-size: 14px;
font-weight: 600;
padding: 11px 18px;
border-radius: var(--r-md);
cursor: pointer;
transition: transform 0.12s, background 0.15s, box-shadow 0.15s, border-color 0.15s;
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn-primary {
color: var(--white);
background: var(--brand);
box-shadow: 0 6px 16px rgba(91, 91, 240, 0.28);
}
.btn-primary:hover { background: var(--brand-d); }
.btn-link {
color: var(--brand-700);
background: transparent;
padding: 11px 8px;
}
.btn-link:hover { background: var(--brand-50); }
.btn-ghost {
color: var(--ink-2);
background: var(--white);
border-color: var(--line-2);
}
.btn-ghost:hover { background: var(--bg); }
.icon-btn {
display: grid;
place-items: center;
width: 32px;
height: 32px;
border: 0;
border-radius: var(--r-sm);
color: var(--muted);
background: transparent;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.icon-btn:hover { background: var(--bg); color: var(--ink); }
/* ---------- Create form ---------- */
.create-form { padding: 22px 22px 20px; animation: rise 0.28s ease; }
@keyframes rise {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.form-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.form-head h2 { font-size: 16px; font-weight: 700; }
.field { display: block; margin-bottom: 16px; }
.field-label {
display: block;
font-size: 12.5px;
font-weight: 600;
color: var(--ink-2);
margin-bottom: 6px;
}
.input {
width: 100%;
font: inherit;
font-size: 14px;
color: var(--ink);
padding: 11px 13px;
border: 1px solid var(--line-2);
border-radius: var(--r-md);
background: var(--white);
transition: border-color 0.15s, box-shadow 0.15s;
}
.input::placeholder { color: var(--muted); }
.input:focus {
outline: none;
border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(91, 91, 240, 0.18);
}
.swatches { display: flex; gap: 10px; }
.swatch {
width: 28px;
height: 28px;
border-radius: 9px;
border: 2px solid transparent;
background: var(--sw);
cursor: pointer;
position: relative;
transition: transform 0.12s, box-shadow 0.15s;
}
.swatch:hover { transform: scale(1.08); }
.swatch.is-active {
box-shadow: 0 0 0 2px var(--white), 0 0 0 4px var(--sw);
}
.form-foot {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 4px;
}
/* ---------- Project list ---------- */
.project-list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 14px;
}
.project-card {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-1);
animation: rise 0.3s ease;
}
.project-card:hover { box-shadow: var(--sh-2); }
.pc-mark {
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: 12px;
color: var(--white);
font-weight: 800;
font-size: 15px;
flex: none;
}
.pc-body { min-width: 0; }
.pc-name {
font-size: 14.5px;
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pc-meta {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--muted);
margin-top: 2px;
}
.pc-badge {
font-size: 10px;
font-weight: 700;
padding: 1px 7px;
border-radius: 999px;
color: var(--ok);
background: rgba(47, 158, 111, 0.12);
}
/* ---------- Variant: icon ---------- */
.stage[data-variant="icon"] .art-illustration { display: none; }
.stage[data-variant="icon"] .art-icon { display: grid; }
/* ---------- Variant: minimal ---------- */
.stage[data-variant="minimal"] .art-illustration { display: none; }
.stage[data-variant="minimal"] .art-icon { display: none; }
.stage[data-variant="minimal"] .empty { padding: 36px 28px; }
.stage[data-variant="minimal"] .empty::before { display: none; }
.stage[data-variant="minimal"] .empty-hints { display: none; }
.stage[data-variant="minimal"] .empty-sub { margin-top: 6px; }
/* ---------- Layout: full page ---------- */
.stage[data-layout="full"] .empty {
border: 0;
box-shadow: none;
background:
radial-gradient(640px 280px at 50% -60px, rgba(91, 91, 240, 0.06), transparent 70%),
var(--bg);
border-radius: var(--r-lg);
padding: 64px 28px 72px;
min-height: 420px;
justify-content: center;
}
.stage[data-layout="full"] .empty::before { display: none; }
.stage[data-layout="full"] .empty-title { font-size: 26px; }
.stage[data-layout="full"] .empty-sub { font-size: 15.5px; max-width: 420px; }
/* ---------- Toast ---------- */
.toast-wrap {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%);
display: flex;
flex-direction: column;
gap: 10px;
z-index: 50;
pointer-events: none;
}
.toast {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 16px;
border-radius: var(--r-md);
background: var(--ink);
color: var(--white);
font-size: 13.5px;
font-weight: 600;
box-shadow: var(--sh-2);
animation: toastIn 0.28s ease;
}
.toast.is-out { animation: toastOut 0.25s ease forwards; }
.toast .t-ico { display: grid; place-items: center; color: var(--accent); }
@keyframes toastIn {
from { opacity: 0; transform: translateY(10px) scale(0.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes toastOut {
to { opacity: 0; transform: translateY(8px); }
}
/* ---------- Responsive ---------- */
@media (max-width: 520px) {
.page { padding: 18px 14px 56px; }
.topnav .nav-link { display: none; }
.topnav .nav-link.is-active { display: inline-flex; }
.toolbar { align-items: flex-start; }
.variant-switch { width: 100%; }
.seg { flex: 1; }
.seg-btn { flex: 1; text-align: center; }
.empty { padding: 30px 18px 34px; }
.empty-actions { flex-direction: column; align-items: stretch; }
.empty-actions .btn { justify-content: center; }
.empty-hints { gap: 12px; }
.stage[data-layout="full"] .empty { padding: 44px 18px 48px; min-height: 360px; }
.form-foot { flex-direction: column-reverse; }
.form-foot .btn { width: 100%; justify-content: center; }
.project-list { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}(function () {
"use strict";
var stage = document.getElementById("stage");
var empty = document.getElementById("empty");
var createBtn = document.getElementById("createBtn");
var importBtn = document.getElementById("importBtn");
var form = document.getElementById("createForm");
var projName = document.getElementById("projName");
var swatches = document.getElementById("swatches");
var formCreate = document.getElementById("formCreate");
var formCancel = document.getElementById("formCancel");
var cancelCreate = document.getElementById("cancelCreate");
var list = document.getElementById("projectList");
var toastWrap = document.getElementById("toastWrap");
var accent = "#5b5bf0";
/* ---------- Toast helper ---------- */
function toast(msg, kind) {
var t = document.createElement("div");
t.className = "toast";
var ico = kind === "info"
? '<path d="M12 8h.01M11 12h1v4h1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/>'
: '<path d="M5 12l4.5 4.5L19 7" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>';
t.innerHTML =
'<span class="t-ico"><svg viewBox="0 0 24 24" width="18" height="18" fill="none">' +
ico + "</svg></span><span>" + msg + "</span>";
toastWrap.appendChild(t);
setTimeout(function () {
t.classList.add("is-out");
setTimeout(function () { t.remove(); }, 260);
}, 2400);
}
/* ---------- Variant + layout switcher ---------- */
function bindSeg(attr, ariaKey) {
var btns = document.querySelectorAll("[data-" + attr + "]");
Array.prototype.forEach.call(btns, function (btn) {
btn.addEventListener("click", function () {
var group = btn.closest(".seg");
Array.prototype.forEach.call(group.querySelectorAll(".seg-btn"), function (b) {
b.classList.remove("is-active");
if (ariaKey) b.setAttribute(ariaKey, "false");
});
btn.classList.add("is-active");
if (ariaKey) btn.setAttribute(ariaKey, "true");
stage.setAttribute("data-" + attr, btn.getAttribute("data-" + attr));
});
});
}
bindSeg("variant", "aria-selected");
bindSeg("layout", "aria-pressed");
/* ---------- Reveal create form ---------- */
function openForm() {
empty.style.display = "none";
form.hidden = false;
projName.value = "";
setAccent("#5b5bf0", swatches.querySelector(".swatch"));
requestAnimationFrame(function () { projName.focus(); });
}
function closeForm(refocus) {
form.hidden = true;
empty.style.display = "";
if (refocus) createBtn.focus();
}
createBtn.addEventListener("click", openForm);
cancelCreate.addEventListener("click", function () { closeForm(true); });
formCancel.addEventListener("click", function () { closeForm(true); });
importBtn.addEventListener("click", function () {
toast("Import wizard would open here", "info");
});
/* ---------- Accent swatches ---------- */
function setAccent(color, el) {
accent = color;
Array.prototype.forEach.call(swatches.querySelectorAll(".swatch"), function (s) {
s.classList.remove("is-active");
s.setAttribute("aria-checked", "false");
});
if (el) { el.classList.add("is-active"); el.setAttribute("aria-checked", "true"); }
}
swatches.addEventListener("click", function (e) {
var sw = e.target.closest(".swatch");
if (!sw) return;
setAccent(sw.getAttribute("data-color"), sw);
});
/* ---------- Initials helper ---------- */
function initials(name) {
var parts = name.trim().split(/\s+/).filter(Boolean);
if (!parts.length) return "P";
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
return (parts[0][0] + parts[1][0]).toUpperCase();
}
/* ---------- Create a project (faux) ---------- */
function addProject(name, color) {
list.hidden = false;
var li = document.createElement("li");
li.className = "project-card";
li.innerHTML =
'<span class="pc-mark" style="background:' + color + '">' + initials(name) + "</span>" +
'<span class="pc-body">' +
'<span class="pc-name"></span>' +
'<span class="pc-meta"><span class="pc-badge">Active</span> Just now · 0 boards</span>' +
"</span>";
li.querySelector(".pc-name").textContent = name;
list.insertBefore(li, list.firstChild);
}
form.addEventListener("submit", function (e) {
e.preventDefault();
var name = projName.value.trim();
if (!name) {
projName.focus();
projName.style.borderColor = "var(--danger)";
toast("Give your project a name first", "info");
return;
}
projName.style.borderColor = "";
addProject(name, accent);
form.hidden = true;
empty.style.display = "";
toast('Created "' + name + '"');
createBtn.focus();
});
projName.addEventListener("input", function () {
if (projName.value.trim()) projName.style.borderColor = "";
});
/* ---------- Esc closes the form ---------- */
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !form.hidden) {
closeForm(true);
}
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Empty States — First-use empty 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="page">
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none">
<rect x="2.5" y="2.5" width="8" height="8" rx="2" fill="currentColor"/>
<rect x="13.5" y="2.5" width="8" height="8" rx="2" fill="currentColor" opacity=".55"/>
<rect x="2.5" y="13.5" width="8" height="8" rx="2" fill="currentColor" opacity=".55"/>
<rect x="13.5" y="13.5" width="8" height="8" rx="2" fill="currentColor"/>
</svg>
</span>
<span class="brand-name">Loomstack</span>
<span class="badge badge-plan">Free</span>
</div>
<nav class="topnav" aria-label="Primary">
<button class="nav-link" type="button">Overview</button>
<button class="nav-link is-active" type="button" aria-current="page">Projects</button>
<button class="nav-link" type="button">Members</button>
<span class="avatar" aria-hidden="true">RH</span>
</nav>
</header>
<section class="toolbar">
<div class="toolbar-titles">
<h1>Projects</h1>
<p class="toolbar-sub">Spaces where your team plans, builds and ships.</p>
</div>
<div class="variant-switch" role="group" aria-label="Empty state variant">
<span class="variant-label">Style</span>
<div class="seg" role="tablist" aria-label="Illustration style">
<button class="seg-btn is-active" data-variant="illustration" role="tab" aria-selected="true" type="button">Illustration</button>
<button class="seg-btn" data-variant="icon" role="tab" aria-selected="false" type="button">Icon</button>
<button class="seg-btn" data-variant="minimal" role="tab" aria-selected="false" type="button">Minimal</button>
</div>
<div class="seg seg-layout" role="group" aria-label="Layout">
<button class="seg-btn is-active" data-layout="inline" type="button" aria-pressed="true">Inline card</button>
<button class="seg-btn" data-layout="full" type="button" aria-pressed="false">Full page</button>
</div>
</div>
</section>
<!-- Empty state host -->
<section class="stage" data-variant="illustration" data-layout="inline" id="stage">
<div class="empty card" id="empty" role="region" aria-label="No projects">
<div class="empty-art" id="emptyArt" aria-hidden="true">
<!-- Illustration -->
<svg class="art-illustration" viewBox="0 0 240 168" width="240" height="168" fill="none" role="img" aria-label="An empty project board">
<ellipse cx="120" cy="150" rx="86" ry="12" fill="#101322" opacity=".06"/>
<rect x="44" y="36" width="152" height="100" rx="14" fill="var(--brand-50)" stroke="var(--brand)" stroke-opacity=".25"/>
<rect x="44" y="36" width="152" height="26" rx="14" fill="var(--brand)" opacity=".12"/>
<circle cx="60" cy="49" r="3.4" fill="var(--brand)"/>
<circle cx="71" cy="49" r="3.4" fill="var(--accent)"/>
<circle cx="82" cy="49" r="3.4" fill="var(--warn)"/>
<rect x="58" y="76" width="36" height="44" rx="7" fill="var(--white)" stroke="var(--line-2)"/>
<rect x="64" y="84" width="24" height="5" rx="2.5" fill="var(--brand)" opacity=".5"/>
<rect x="64" y="94" width="18" height="4" rx="2" fill="var(--muted)" opacity=".4"/>
<rect x="102" y="76" width="36" height="44" rx="7" fill="var(--white)" stroke="var(--line-2)" stroke-dasharray="4 4"/>
<rect x="146" y="76" width="36" height="44" rx="7" fill="var(--white)" stroke="var(--line-2)" stroke-dasharray="4 4"/>
<g transform="translate(120 74)">
<circle r="22" fill="var(--accent)"/>
<path d="M0 -11 V11 M-11 0 H11" stroke="#fff" stroke-width="3.4" stroke-linecap="round"/>
</g>
<path d="M30 30 l5 -5 M205 26 l6 2 M196 118 l7 4" stroke="var(--accent)" stroke-width="2.4" stroke-linecap="round"/>
</svg>
<!-- Icon -->
<span class="art-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="34" height="34" fill="none">
<path d="M3 7.5A2.5 2.5 0 0 1 5.5 5h3.2c.6 0 1.2.25 1.6.7L11.7 7H18.5A2.5 2.5 0 0 1 21 9.5v7A2.5 2.5 0 0 1 18.5 19h-13A2.5 2.5 0 0 1 3 16.5z" stroke="currentColor" stroke-width="1.7"/>
</svg>
</span>
</div>
<h2 class="empty-title">No projects yet</h2>
<p class="empty-sub">Projects keep your boards, files and discussions in one place. Create your first one to get the team moving.</p>
<div class="empty-actions">
<button class="btn btn-primary" id="createBtn" type="button">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" aria-hidden="true"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
Create your first project
</button>
<button class="btn btn-link" id="importBtn" type="button">
<svg viewBox="0 0 24 24" width="17" height="17" fill="none" aria-hidden="true"><path d="M12 16V4m0 12-4-4m4 4 4-4M5 19h14" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
Import projects
</button>
</div>
<ul class="empty-hints" aria-label="What you can do next">
<li><span class="dot dot-brand"></span> Invite teammates</li>
<li><span class="dot dot-accent"></span> Connect a repo</li>
<li><span class="dot dot-warn"></span> Start from a template</li>
</ul>
</div>
<!-- Inline create form (revealed) -->
<form class="create-form card" id="createForm" hidden>
<div class="form-head">
<h2>New project</h2>
<button class="icon-btn" id="cancelCreate" type="button" aria-label="Cancel">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</div>
<label class="field">
<span class="field-label">Project name</span>
<input class="input" id="projName" type="text" placeholder="e.g. Atlas Mobile" autocomplete="off" maxlength="48" />
</label>
<label class="field">
<span class="field-label">Accent</span>
<div class="swatches" id="swatches" role="radiogroup" aria-label="Project accent color">
<button type="button" class="swatch is-active" data-color="#5b5bf0" role="radio" aria-checked="true" aria-label="Indigo" style="--sw:#5b5bf0"></button>
<button type="button" class="swatch" data-color="#00b4a6" role="radio" aria-checked="false" aria-label="Teal" style="--sw:#00b4a6"></button>
<button type="button" class="swatch" data-color="#d98a2b" role="radio" aria-checked="false" aria-label="Amber" style="--sw:#d98a2b"></button>
<button type="button" class="swatch" data-color="#d4503e" role="radio" aria-checked="false" aria-label="Coral" style="--sw:#d4503e"></button>
</div>
</label>
<div class="form-foot">
<button class="btn btn-ghost" id="formCancel" type="button">Cancel</button>
<button class="btn btn-primary" id="formCreate" type="submit">Create project</button>
</div>
</form>
<!-- Created list (populated by JS) -->
<ul class="project-list" id="projectList" aria-live="polite" hidden></ul>
</section>
</main>
<div class="toast-wrap" id="toastWrap" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>First-use empty state
A first-run “Projects” screen for the fictional Loomstack workspace, framed by a product chrome of topbar and toolbar so the empty state reads in context. The centerpiece is a hand-drawn inline-SVG board illustration sitting above a clear hierarchy: the headline No projects yet, a one-sentence explanation, a primary Create your first project button, a secondary Import link, and a row of next-step hints. Everything is keyboard-usable with visible focus rings and AA-contrast text.
Three illustration styles ship in one component — a full illustration, a compact rounded icon tile, and a stripped-back minimal layout — toggled live from a segmented control, alongside an inline-card vs full-page layout switch so you can audition each combination. The minimal style drops the artwork and hints for the densest possible state, while full-page centers the content in a tall hero region.
Clicking the primary CTA swaps the empty block for an inline create form with a name field and four accent swatches (a keyboard-navigable radiogroup). Submitting builds a faux project card — initials avatar in the chosen accent, an Active badge, and a “Just now” timestamp — prepends it to the live list, and fires a confirming toast. Esc or Cancel returns to the empty state, the Import link raises its own toast, and the whole layout collapses to a single column down to 360px.
Illustrative UI only — fictional workspace, projects, and data.