10 / 20
All 20 in 20 CSS Empty States
- 01No Search Results State
- 02First-Time Onboarding State
- 03Empty Shopping Cart State
- 04Inbox Zero / All Caught Up State
- 05Empty Data Table / No Rows State
- 06Filtered List No Matches State
- 07Empty Dashboard / No Analytics Data
- 08500 Server Error Page Layout
- 09Empty Wishlist / Favorites State
- 10Empty Notification Center Flyout
- 11Failed to Load / Connection Error State
- 12Offline / No Internet Connection State
- 13Permission Denied / Restricted Access State
- 14404 Page Not Found Layout
- 15Deleted or Archived Items State
- 16Skeleton Loading to Empty State Transition
- 17Accessible Empty State (ARIA Live)
- 18Pure CSS Illustrated Empty State
- 19Dark Mode Compatible Empty State
- 20Inline / Micro Empty State Component
Empty Notification Center Flyout
A navbar bell that opens a real notification flyout with zero JavaScript: a single checkbox drives the open state and :has() lifts it to the panel, the bell, and the page behind it, which blurs and dims as the flyout takes focus. Three radio-driven tabs each carry their own empty state.
Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149
Nucleo · Affiliate link
The code
<div class="est-10">
<header class="est-10__bar">
<span class="est-10__brand">Ledgerline</span>
<nav class="est-10__nav">
<a href="#est-10">Accounts</a>
<a href="#est-10">Transfers</a>
<a href="#est-10">Reports</a>
</nav>
<div class="est-10__right">
<input class="est-10__sr" type="checkbox" id="est-10-open" checked>
<label class="est-10__bell" for="est-10-open" title="Notifications">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M6.5 16.5V11a5.5 5.5 0 0 1 11 0v5.5l1.5 2H5z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M10 19.5a2 2 0 0 0 4 0" stroke="currentColor" stroke-width="1.6"/></svg>
<span class="est-10__vh">Notifications</span>
</label>
<span class="est-10__ava" aria-hidden="true">JS</span>
<div class="est-10__flyout" role="dialog" aria-label="Notifications">
<div class="est-10__fhead">
<span class="est-10__ftitle">Notifications</span>
<label class="est-10__fclose" for="est-10-open" aria-label="Close">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M7 7l10 10M17 7 7 17" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>
</label>
</div>
<div class="est-10__ftabs">
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-a" checked>
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-b">
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-c">
<div class="est-10__ftabrow">
<label class="est-10__ftab" for="est-10-a">All</label>
<label class="est-10__ftab" for="est-10-b">Approvals</label>
<label class="est-10__ftab" for="est-10-c">Mentions</label>
</div>
<div class="est-10__fpanel" data-p="a">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M5 12.5 9.5 17 19 7.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<p class="est-10__eh">You are up to date</p>
<p class="est-10__ep">Everything since Monday has been read. New activity lands here as it happens.</p>
</div>
<div class="est-10__fpanel" data-p="b">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M12 4.5 19 8v5c0 3.6-2.9 5.9-7 6.9-4.1-1-7-3.3-7-6.9V8z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
</span>
<p class="est-10__eh">No approvals waiting</p>
<p class="est-10__ep">Transfers above your limit appear here for a second signature.</p>
</div>
<div class="est-10__fpanel" data-p="c">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M15 8.5a3.5 3.5 0 1 0-3 5.3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="12" cy="12" r="8" stroke="currentColor" stroke-width="1.6"/></svg>
</span>
<p class="est-10__eh">Nobody has tagged you</p>
<p class="est-10__ep">Mentions in comments and reconciliation notes show up here.</p>
</div>
</div>
<div class="est-10__ffoot">
<a href="#est-10">Notification settings</a>
</div>
</div>
</div>
</header>
<main class="est-10__page" aria-hidden="true">
<div class="est-10__pcol">
<div class="est-10__block" data-b="head"></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
</div>
<div class="est-10__pcol">
<div class="est-10__block" data-b="chart"></div>
<div class="est-10__block" data-b="note"></div>
</div>
</main>
</div><div class="est-10">
<header class="est-10__bar">
<span class="est-10__brand">Ledgerline</span>
<nav class="est-10__nav">
<a href="#est-10">Accounts</a>
<a href="#est-10">Transfers</a>
<a href="#est-10">Reports</a>
</nav>
<div class="est-10__right">
<input class="est-10__sr" type="checkbox" id="est-10-open" checked>
<label class="est-10__bell" for="est-10-open" title="Notifications">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M6.5 16.5V11a5.5 5.5 0 0 1 11 0v5.5l1.5 2H5z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M10 19.5a2 2 0 0 0 4 0" stroke="currentColor" stroke-width="1.6"/></svg>
<span class="est-10__vh">Notifications</span>
</label>
<span class="est-10__ava" aria-hidden="true">JS</span>
<div class="est-10__flyout" role="dialog" aria-label="Notifications">
<div class="est-10__fhead">
<span class="est-10__ftitle">Notifications</span>
<label class="est-10__fclose" for="est-10-open" aria-label="Close">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M7 7l10 10M17 7 7 17" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>
</label>
</div>
<div class="est-10__ftabs">
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-a" checked>
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-b">
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-c">
<div class="est-10__ftabrow">
<label class="est-10__ftab" for="est-10-a">All</label>
<label class="est-10__ftab" for="est-10-b">Approvals</label>
<label class="est-10__ftab" for="est-10-c">Mentions</label>
</div>
<div class="est-10__fpanel" data-p="a">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M5 12.5 9.5 17 19 7.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<p class="est-10__eh">You are up to date</p>
<p class="est-10__ep">Everything since Monday has been read. New activity lands here as it happens.</p>
</div>
<div class="est-10__fpanel" data-p="b">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M12 4.5 19 8v5c0 3.6-2.9 5.9-7 6.9-4.1-1-7-3.3-7-6.9V8z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
</span>
<p class="est-10__eh">No approvals waiting</p>
<p class="est-10__ep">Transfers above your limit appear here for a second signature.</p>
</div>
<div class="est-10__fpanel" data-p="c">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M15 8.5a3.5 3.5 0 1 0-3 5.3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="12" cy="12" r="8" stroke="currentColor" stroke-width="1.6"/></svg>
</span>
<p class="est-10__eh">Nobody has tagged you</p>
<p class="est-10__ep">Mentions in comments and reconciliation notes show up here.</p>
</div>
</div>
<div class="est-10__ffoot">
<a href="#est-10">Notification settings</a>
</div>
</div>
</div>
</header>
<main class="est-10__page" aria-hidden="true">
<div class="est-10__pcol">
<div class="est-10__block" data-b="head"></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
</div>
<div class="est-10__pcol">
<div class="est-10__block" data-b="chart"></div>
<div class="est-10__block" data-b="note"></div>
</div>
</main>
</div>@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&display=swap');
.est-10 {
--bg: #f5f6f8;
--surface: #ffffff;
--sunk: #edeff2;
--line: #e3e6ea;
--line-2: #d2d6dc;
--ink: #14161a;
--muted: #5b606a;
--faint: #8a8f99;
--accent: #37507a;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
grid-template-rows: auto minmax(0,1fr);
box-sizing: border-box;
background: var(--bg);
color: var(--ink);
color-scheme: light;
font-synthesis: none;
font-family: 'Geist',ui-sans-serif,system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.est-10 *,
.est-10 *::before,
.est-10 *::after {
box-sizing: border-box;
}
.est-10 [hidden] {
display: none !important;
}
.est-10__vh {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.est-10__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
opacity: 0;
margin: 0;
pointer-events: none;
}
@supports (color: oklch(0.7 0.1 300)) {
.est-10 {
--bg: oklch(0.972 0.004 255);
--surface: oklch(1 0 0);
--sunk: oklch(0.952 0.005 255);
--line: oklch(0.918 0.006 255);
--line-2: oklch(0.865 0.008 255);
--ink: oklch(0.175 0.009 260);
--muted: oklch(0.465 0.012 260);
--faint: oklch(0.625 0.013 260);
--accent: oklch(0.4 0.075 262);
}
}
.est-10__bar {
position: relative;
z-index: 5;
display: flex;
align-items: center;
gap: 1.5rem;
padding: .85rem clamp(1rem,3vw,2.25rem);
border-block-end: 1px solid var(--line);
background: var(--surface);
}
.est-10__brand {
font-size: .95rem;
font-weight: 600;
letter-spacing: -.02em;
}
.est-10__nav {
display: flex;
gap: 1.15rem;
}
.est-10__nav a {
font-size: .86rem;
color: var(--muted);
text-decoration: none;
transition: color .16s;
}
.est-10__nav a:hover {
color: var(--ink);
}
.est-10__right {
position: relative;
margin-inline-start: auto;
display: flex;
align-items: center;
gap: .6rem;
}
.est-10__bell {
position: relative;
inline-size: 2.4rem;
block-size: 2.4rem;
display: grid;
place-items: center;
cursor: pointer;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 10px;
transition: background .16s, border-color .16s;
}
.est-10__bell svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.est-10__bell:hover {
background: var(--sunk);
}
.est-10__right:has(#est-10-open:checked) .est-10__bell {
background: var(--sunk);
border-color: var(--line-2);
}
#est-10-open:focus-visible + .est-10__bell {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.est-10__ava {
inline-size: 2rem;
block-size: 2rem;
display: grid;
place-items: center;
border-radius: 50%;
font-size: .7rem;
font-weight: 600;
color: #fff;
background: var(--accent);
}
.est-10__flyout {
position: absolute;
inset-block-start: calc(100% + .65rem);
inset-inline-end: 0;
inline-size: min(22.5rem,calc(100vw - 2rem));
display: grid;
opacity: 0;
translate: 0 -.4rem;
scale: .985;
pointer-events: none;
transform-origin: 100% 0;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 16px;
box-shadow: 0 1px 2px rgba(18,20,24,.05), 0 30px 60px -34px rgba(18,20,24,.35);
transition: opacity .2s ease, translate .22s cubic-bezier(.2,.7,.3,1), scale .22s cubic-bezier(.2,.7,.3,1);
}
.est-10__flyout::before {
content: '';
position: absolute;
inset-block-start: -.36rem;
inset-inline-end: 1.1rem;
inline-size: .7rem;
block-size: .7rem;
rotate: 45deg;
background: var(--surface);
border-inline-start: 1px solid var(--line);
border-block-start: 1px solid var(--line);
border-start-start-radius: 3px;
}
.est-10__right:has(#est-10-open:checked) .est-10__flyout {
opacity: 1;
translate: 0 0;
scale: 1;
pointer-events: auto;
}
.est-10__fhead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: .85rem 1rem;
border-block-end: 1px solid var(--line);
}
.est-10__ftitle {
font-size: .9rem;
font-weight: 600;
letter-spacing: -.015em;
}
.est-10__fclose {
inline-size: 1.75rem;
block-size: 1.75rem;
display: grid;
place-items: center;
cursor: pointer;
color: var(--faint);
border-radius: 7px;
transition: color .16s, background .16s;
}
.est-10__fclose svg {
inline-size: .85rem;
block-size: .85rem;
}
.est-10__fclose:hover {
color: var(--ink);
background: var(--sunk);
}
.est-10__ftabs {
display: grid;
}
.est-10__ftabrow {
display: flex;
gap: .15rem;
padding: .6rem .7rem 0;
}
.est-10__ftab {
flex: 1 1 auto;
padding: .4rem .5rem;
text-align: center;
cursor: pointer;
font-size: .79rem;
font-weight: 500;
color: var(--muted);
border-radius: 8px;
transition: color .16s, background .16s;
}
.est-10__ftab:hover {
color: var(--ink);
background: var(--sunk);
}
#est-10-a:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-a"],
#est-10-b:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-b"],
#est-10-c:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-c"] {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.est-10__ftabs:has(#est-10-a:checked) .est-10__ftab[for="est-10-a"],
.est-10__ftabs:has(#est-10-b:checked) .est-10__ftab[for="est-10-b"],
.est-10__ftabs:has(#est-10-c:checked) .est-10__ftab[for="est-10-c"] {
color: var(--ink);
background: var(--sunk);
box-shadow: inset 0 0 0 1px var(--line);
}
.est-10__fpanel {
display: none;
justify-items: center;
text-align: center;
gap: .35rem;
padding: 2rem 1.35rem 1.75rem;
}
.est-10__ftabs:has(#est-10-a:checked) [data-p="a"],
.est-10__ftabs:has(#est-10-b:checked) [data-p="b"],
.est-10__ftabs:has(#est-10-c:checked) [data-p="c"] {
display: grid;
animation: est-10-in .35s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes est-10-in {
from {
opacity: 0;
translate: 0 5px;
}
to {
opacity: 1;
translate: 0 0;
}
}
.est-10__glyph {
position: relative;
inline-size: 3rem;
block-size: 3rem;
display: grid;
place-items: center;
margin-block-end: .5rem;
color: var(--accent);
background: color-mix(in oklab, var(--accent) 8%, var(--surface));
border-radius: 50%;
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 18%, transparent);
}
.est-10__glyph::after {
content: '';
position: absolute;
inset: -.45rem;
border-radius: 50%;
border: 1px solid color-mix(in oklab, var(--accent) 15%, transparent);
animation: est-10-breathe 5s ease-in-out infinite;
}
@keyframes est-10-breathe {
0%,
100% {
opacity: .9;
scale: 1;
}
50% {
opacity: .35;
scale: 1.05;
}
}
.est-10__glyph svg {
inline-size: 1.3rem;
block-size: 1.3rem;
}
.est-10__eh {
margin: 0;
font-size: .95rem;
font-weight: 600;
letter-spacing: -.018em;
}
.est-10__ep {
margin: 0;
max-inline-size: 30ch;
font-size: .82rem;
line-height: 1.5;
color: var(--muted);
text-wrap: pretty;
}
.est-10__ffoot {
padding: .75rem 1rem;
border-block-start: 1px solid var(--line);
background: var(--sunk);
border-end-start-radius: 16px;
border-end-end-radius: 16px;
}
.est-10__ffoot a {
font-size: .8rem;
color: var(--accent);
text-decoration: none;
}
.est-10__ffoot a:hover {
text-decoration: underline;
text-underline-offset: 3px;
}
.est-10__page {
min-inline-size: 0;
padding: clamp(1.25rem,3.5vw,2.5rem) clamp(1rem,3vw,2.25rem);
display: grid;
gap: 1.25rem;
grid-template-columns: repeat(auto-fit,minmax(min(18rem,100%),1fr));
align-content: start;
filter: blur(0);
transition: filter .3s ease, opacity .3s ease;
}
.est-10:has(#est-10-open:checked) .est-10__page {
filter: blur(1.5px);
opacity: .65;
}
.est-10__pcol {
display: grid;
gap: .75rem;
align-content: start;
}
.est-10__block {
background: var(--surface);
border: 1px solid var(--line);
border-radius: 14px;
}
.est-10__block[data-b="head"] {
block-size: 3.25rem;
}
.est-10__block[data-b="chart"] {
block-size: 12rem;
background: linear-gradient(180deg, transparent 55%, color-mix(in oklab, var(--accent) 7%, transparent)), repeating-linear-gradient(180deg, var(--line) 0 1px, transparent 1px 3rem), var(--surface);
}
.est-10__block[data-b="note"] {
block-size: 6rem;
}
.est-10__row {
display: grid;
grid-template-columns: minmax(0,2fr) minmax(0,1fr) minmax(0,1fr);
gap: .75rem;
padding: .9rem 1rem;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 12px;
}
.est-10__row span {
block-size: .6rem;
border-radius: 3px;
background: var(--sunk);
}
.est-10__row span:first-child {
background: color-mix(in oklab, var(--line-2) 75%, transparent);
}
@media (prefers-reduced-motion: reduce) {
.est-10 *,
.est-10 *::before,
.est-10 *::after {
animation: none !important;
transition: none !important;
}
}
@media (forced-colors: active) {
.est-10__flyout,
.est-10__bell,
.est-10__glyph {
border-color: CanvasText;
}
}@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&display=swap');
.est-10 {
--bg: #f5f6f8;
--surface: #ffffff;
--sunk: #edeff2;
--line: #e3e6ea;
--line-2: #d2d6dc;
--ink: #14161a;
--muted: #5b606a;
--faint: #8a8f99;
--accent: #37507a;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
grid-template-rows: auto minmax(0,1fr);
box-sizing: border-box;
background: var(--bg);
color: var(--ink);
color-scheme: light;
font-synthesis: none;
font-family: 'Geist',ui-sans-serif,system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.est-10 *,
.est-10 *::before,
.est-10 *::after {
box-sizing: border-box;
}
.est-10 [hidden] {
display: none !important;
}
.est-10__vh {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.est-10__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
opacity: 0;
margin: 0;
pointer-events: none;
}
@supports (color: oklch(0.7 0.1 300)) {
.est-10 {
--bg: oklch(0.972 0.004 255);
--surface: oklch(1 0 0);
--sunk: oklch(0.952 0.005 255);
--line: oklch(0.918 0.006 255);
--line-2: oklch(0.865 0.008 255);
--ink: oklch(0.175 0.009 260);
--muted: oklch(0.465 0.012 260);
--faint: oklch(0.625 0.013 260);
--accent: oklch(0.4 0.075 262);
}
}
.est-10__bar {
position: relative;
z-index: 5;
display: flex;
align-items: center;
gap: 1.5rem;
padding: .85rem clamp(1rem,3vw,2.25rem);
border-block-end: 1px solid var(--line);
background: var(--surface);
}
.est-10__brand {
font-size: .95rem;
font-weight: 600;
letter-spacing: -.02em;
}
.est-10__nav {
display: flex;
gap: 1.15rem;
}
.est-10__nav a {
font-size: .86rem;
color: var(--muted);
text-decoration: none;
transition: color .16s;
}
.est-10__nav a:hover {
color: var(--ink);
}
.est-10__right {
position: relative;
margin-inline-start: auto;
display: flex;
align-items: center;
gap: .6rem;
}
.est-10__bell {
position: relative;
inline-size: 2.4rem;
block-size: 2.4rem;
display: grid;
place-items: center;
cursor: pointer;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 10px;
transition: background .16s, border-color .16s;
}
.est-10__bell svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.est-10__bell:hover {
background: var(--sunk);
}
.est-10__right:has(#est-10-open:checked) .est-10__bell {
background: var(--sunk);
border-color: var(--line-2);
}
#est-10-open:focus-visible + .est-10__bell {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.est-10__ava {
inline-size: 2rem;
block-size: 2rem;
display: grid;
place-items: center;
border-radius: 50%;
font-size: .7rem;
font-weight: 600;
color: #fff;
background: var(--accent);
}
.est-10__flyout {
position: absolute;
inset-block-start: calc(100% + .65rem);
inset-inline-end: 0;
inline-size: min(22.5rem,calc(100vw - 2rem));
display: grid;
opacity: 0;
translate: 0 -.4rem;
scale: .985;
pointer-events: none;
transform-origin: 100% 0;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 16px;
box-shadow: 0 1px 2px rgba(18,20,24,.05), 0 30px 60px -34px rgba(18,20,24,.35);
transition: opacity .2s ease, translate .22s cubic-bezier(.2,.7,.3,1), scale .22s cubic-bezier(.2,.7,.3,1);
}
.est-10__flyout::before {
content: '';
position: absolute;
inset-block-start: -.36rem;
inset-inline-end: 1.1rem;
inline-size: .7rem;
block-size: .7rem;
rotate: 45deg;
background: var(--surface);
border-inline-start: 1px solid var(--line);
border-block-start: 1px solid var(--line);
border-start-start-radius: 3px;
}
.est-10__right:has(#est-10-open:checked) .est-10__flyout {
opacity: 1;
translate: 0 0;
scale: 1;
pointer-events: auto;
}
.est-10__fhead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: .85rem 1rem;
border-block-end: 1px solid var(--line);
}
.est-10__ftitle {
font-size: .9rem;
font-weight: 600;
letter-spacing: -.015em;
}
.est-10__fclose {
inline-size: 1.75rem;
block-size: 1.75rem;
display: grid;
place-items: center;
cursor: pointer;
color: var(--faint);
border-radius: 7px;
transition: color .16s, background .16s;
}
.est-10__fclose svg {
inline-size: .85rem;
block-size: .85rem;
}
.est-10__fclose:hover {
color: var(--ink);
background: var(--sunk);
}
.est-10__ftabs {
display: grid;
}
.est-10__ftabrow {
display: flex;
gap: .15rem;
padding: .6rem .7rem 0;
}
.est-10__ftab {
flex: 1 1 auto;
padding: .4rem .5rem;
text-align: center;
cursor: pointer;
font-size: .79rem;
font-weight: 500;
color: var(--muted);
border-radius: 8px;
transition: color .16s, background .16s;
}
.est-10__ftab:hover {
color: var(--ink);
background: var(--sunk);
}
#est-10-a:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-a"],
#est-10-b:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-b"],
#est-10-c:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-c"] {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.est-10__ftabs:has(#est-10-a:checked) .est-10__ftab[for="est-10-a"],
.est-10__ftabs:has(#est-10-b:checked) .est-10__ftab[for="est-10-b"],
.est-10__ftabs:has(#est-10-c:checked) .est-10__ftab[for="est-10-c"] {
color: var(--ink);
background: var(--sunk);
box-shadow: inset 0 0 0 1px var(--line);
}
.est-10__fpanel {
display: none;
justify-items: center;
text-align: center;
gap: .35rem;
padding: 2rem 1.35rem 1.75rem;
}
.est-10__ftabs:has(#est-10-a:checked) [data-p="a"],
.est-10__ftabs:has(#est-10-b:checked) [data-p="b"],
.est-10__ftabs:has(#est-10-c:checked) [data-p="c"] {
display: grid;
animation: est-10-in .35s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes est-10-in {
from {
opacity: 0;
translate: 0 5px;
}
to {
opacity: 1;
translate: 0 0;
}
}
.est-10__glyph {
position: relative;
inline-size: 3rem;
block-size: 3rem;
display: grid;
place-items: center;
margin-block-end: .5rem;
color: var(--accent);
background: color-mix(in oklab, var(--accent) 8%, var(--surface));
border-radius: 50%;
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 18%, transparent);
}
.est-10__glyph::after {
content: '';
position: absolute;
inset: -.45rem;
border-radius: 50%;
border: 1px solid color-mix(in oklab, var(--accent) 15%, transparent);
animation: est-10-breathe 5s ease-in-out infinite;
}
@keyframes est-10-breathe {
0%,
100% {
opacity: .9;
scale: 1;
}
50% {
opacity: .35;
scale: 1.05;
}
}
.est-10__glyph svg {
inline-size: 1.3rem;
block-size: 1.3rem;
}
.est-10__eh {
margin: 0;
font-size: .95rem;
font-weight: 600;
letter-spacing: -.018em;
}
.est-10__ep {
margin: 0;
max-inline-size: 30ch;
font-size: .82rem;
line-height: 1.5;
color: var(--muted);
text-wrap: pretty;
}
.est-10__ffoot {
padding: .75rem 1rem;
border-block-start: 1px solid var(--line);
background: var(--sunk);
border-end-start-radius: 16px;
border-end-end-radius: 16px;
}
.est-10__ffoot a {
font-size: .8rem;
color: var(--accent);
text-decoration: none;
}
.est-10__ffoot a:hover {
text-decoration: underline;
text-underline-offset: 3px;
}
.est-10__page {
min-inline-size: 0;
padding: clamp(1.25rem,3.5vw,2.5rem) clamp(1rem,3vw,2.25rem);
display: grid;
gap: 1.25rem;
grid-template-columns: repeat(auto-fit,minmax(min(18rem,100%),1fr));
align-content: start;
filter: blur(0);
transition: filter .3s ease, opacity .3s ease;
}
.est-10:has(#est-10-open:checked) .est-10__page {
filter: blur(1.5px);
opacity: .65;
}
.est-10__pcol {
display: grid;
gap: .75rem;
align-content: start;
}
.est-10__block {
background: var(--surface);
border: 1px solid var(--line);
border-radius: 14px;
}
.est-10__block[data-b="head"] {
block-size: 3.25rem;
}
.est-10__block[data-b="chart"] {
block-size: 12rem;
background: linear-gradient(180deg, transparent 55%, color-mix(in oklab, var(--accent) 7%, transparent)), repeating-linear-gradient(180deg, var(--line) 0 1px, transparent 1px 3rem), var(--surface);
}
.est-10__block[data-b="note"] {
block-size: 6rem;
}
.est-10__row {
display: grid;
grid-template-columns: minmax(0,2fr) minmax(0,1fr) minmax(0,1fr);
gap: .75rem;
padding: .9rem 1rem;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 12px;
}
.est-10__row span {
block-size: .6rem;
border-radius: 3px;
background: var(--sunk);
}
.est-10__row span:first-child {
background: color-mix(in oklab, var(--line-2) 75%, transparent);
}
@media (prefers-reduced-motion: reduce) {
.est-10 *,
.est-10 *::before,
.est-10 *::after {
animation: none !important;
transition: none !important;
}
}
@media (forced-colors: active) {
.est-10__flyout,
.est-10__bell,
.est-10__glyph {
border-color: CanvasText;
}
}Here's a working CSS Empty State from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Empty Notification Center Flyout
Source: https://codefronts.com/snippets/css-empty-states/empty-notification-center-flyout/
A navbar bell that opens a real notification flyout with zero JavaScript: a single checkbox drives the open state and :has() lifts it to the panel, the bell, and the page behind it, which blurs and dims as the flyout takes focus. Three radio-driven tabs each carry their own empty state.
## HTML
```html
<div class="est-10">
<header class="est-10__bar">
<span class="est-10__brand">Ledgerline</span>
<nav class="est-10__nav">
<a href="#est-10">Accounts</a>
<a href="#est-10">Transfers</a>
<a href="#est-10">Reports</a>
</nav>
<div class="est-10__right">
<input class="est-10__sr" type="checkbox" id="est-10-open" checked>
<label class="est-10__bell" for="est-10-open" title="Notifications">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M6.5 16.5V11a5.5 5.5 0 0 1 11 0v5.5l1.5 2H5z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M10 19.5a2 2 0 0 0 4 0" stroke="currentColor" stroke-width="1.6"/></svg>
<span class="est-10__vh">Notifications</span>
</label>
<span class="est-10__ava" aria-hidden="true">JS</span>
<div class="est-10__flyout" role="dialog" aria-label="Notifications">
<div class="est-10__fhead">
<span class="est-10__ftitle">Notifications</span>
<label class="est-10__fclose" for="est-10-open" aria-label="Close">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M7 7l10 10M17 7 7 17" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>
</label>
</div>
<div class="est-10__ftabs">
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-a" checked>
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-b">
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-c">
<div class="est-10__ftabrow">
<label class="est-10__ftab" for="est-10-a">All</label>
<label class="est-10__ftab" for="est-10-b">Approvals</label>
<label class="est-10__ftab" for="est-10-c">Mentions</label>
</div>
<div class="est-10__fpanel" data-p="a">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M5 12.5 9.5 17 19 7.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<p class="est-10__eh">You are up to date</p>
<p class="est-10__ep">Everything since Monday has been read. New activity lands here as it happens.</p>
</div>
<div class="est-10__fpanel" data-p="b">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M12 4.5 19 8v5c0 3.6-2.9 5.9-7 6.9-4.1-1-7-3.3-7-6.9V8z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
</span>
<p class="est-10__eh">No approvals waiting</p>
<p class="est-10__ep">Transfers above your limit appear here for a second signature.</p>
</div>
<div class="est-10__fpanel" data-p="c">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M15 8.5a3.5 3.5 0 1 0-3 5.3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="12" cy="12" r="8" stroke="currentColor" stroke-width="1.6"/></svg>
</span>
<p class="est-10__eh">Nobody has tagged you</p>
<p class="est-10__ep">Mentions in comments and reconciliation notes show up here.</p>
</div>
</div>
<div class="est-10__ffoot">
<a href="#est-10">Notification settings</a>
</div>
</div>
</div>
</header>
<main class="est-10__page" aria-hidden="true">
<div class="est-10__pcol">
<div class="est-10__block" data-b="head"></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
</div>
<div class="est-10__pcol">
<div class="est-10__block" data-b="chart"></div>
<div class="est-10__block" data-b="note"></div>
</div>
</main>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&display=swap');
.est-10 {
--bg: #f5f6f8;
--surface: #ffffff;
--sunk: #edeff2;
--line: #e3e6ea;
--line-2: #d2d6dc;
--ink: #14161a;
--muted: #5b606a;
--faint: #8a8f99;
--accent: #37507a;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
grid-template-rows: auto minmax(0,1fr);
box-sizing: border-box;
background: var(--bg);
color: var(--ink);
color-scheme: light;
font-synthesis: none;
font-family: 'Geist',ui-sans-serif,system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.est-10 *,
.est-10 *::before,
.est-10 *::after {
box-sizing: border-box;
}
.est-10 [hidden] {
display: none !important;
}
.est-10__vh {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.est-10__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
opacity: 0;
margin: 0;
pointer-events: none;
}
@supports (color: oklch(0.7 0.1 300)) {
.est-10 {
--bg: oklch(0.972 0.004 255);
--surface: oklch(1 0 0);
--sunk: oklch(0.952 0.005 255);
--line: oklch(0.918 0.006 255);
--line-2: oklch(0.865 0.008 255);
--ink: oklch(0.175 0.009 260);
--muted: oklch(0.465 0.012 260);
--faint: oklch(0.625 0.013 260);
--accent: oklch(0.4 0.075 262);
}
}
.est-10__bar {
position: relative;
z-index: 5;
display: flex;
align-items: center;
gap: 1.5rem;
padding: .85rem clamp(1rem,3vw,2.25rem);
border-block-end: 1px solid var(--line);
background: var(--surface);
}
.est-10__brand {
font-size: .95rem;
font-weight: 600;
letter-spacing: -.02em;
}
.est-10__nav {
display: flex;
gap: 1.15rem;
}
.est-10__nav a {
font-size: .86rem;
color: var(--muted);
text-decoration: none;
transition: color .16s;
}
.est-10__nav a:hover {
color: var(--ink);
}
.est-10__right {
position: relative;
margin-inline-start: auto;
display: flex;
align-items: center;
gap: .6rem;
}
.est-10__bell {
position: relative;
inline-size: 2.4rem;
block-size: 2.4rem;
display: grid;
place-items: center;
cursor: pointer;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 10px;
transition: background .16s, border-color .16s;
}
.est-10__bell svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.est-10__bell:hover {
background: var(--sunk);
}
.est-10__right:has(#est-10-open:checked) .est-10__bell {
background: var(--sunk);
border-color: var(--line-2);
}
#est-10-open:focus-visible + .est-10__bell {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.est-10__ava {
inline-size: 2rem;
block-size: 2rem;
display: grid;
place-items: center;
border-radius: 50%;
font-size: .7rem;
font-weight: 600;
color: #fff;
background: var(--accent);
}
.est-10__flyout {
position: absolute;
inset-block-start: calc(100% + .65rem);
inset-inline-end: 0;
inline-size: min(22.5rem,calc(100vw - 2rem));
display: grid;
opacity: 0;
translate: 0 -.4rem;
scale: .985;
pointer-events: none;
transform-origin: 100% 0;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 16px;
box-shadow: 0 1px 2px rgba(18,20,24,.05), 0 30px 60px -34px rgba(18,20,24,.35);
transition: opacity .2s ease, translate .22s cubic-bezier(.2,.7,.3,1), scale .22s cubic-bezier(.2,.7,.3,1);
}
.est-10__flyout::before {
content: '';
position: absolute;
inset-block-start: -.36rem;
inset-inline-end: 1.1rem;
inline-size: .7rem;
block-size: .7rem;
rotate: 45deg;
background: var(--surface);
border-inline-start: 1px solid var(--line);
border-block-start: 1px solid var(--line);
border-start-start-radius: 3px;
}
.est-10__right:has(#est-10-open:checked) .est-10__flyout {
opacity: 1;
translate: 0 0;
scale: 1;
pointer-events: auto;
}
.est-10__fhead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: .85rem 1rem;
border-block-end: 1px solid var(--line);
}
.est-10__ftitle {
font-size: .9rem;
font-weight: 600;
letter-spacing: -.015em;
}
.est-10__fclose {
inline-size: 1.75rem;
block-size: 1.75rem;
display: grid;
place-items: center;
cursor: pointer;
color: var(--faint);
border-radius: 7px;
transition: color .16s, background .16s;
}
.est-10__fclose svg {
inline-size: .85rem;
block-size: .85rem;
}
.est-10__fclose:hover {
color: var(--ink);
background: var(--sunk);
}
.est-10__ftabs {
display: grid;
}
.est-10__ftabrow {
display: flex;
gap: .15rem;
padding: .6rem .7rem 0;
}
.est-10__ftab {
flex: 1 1 auto;
padding: .4rem .5rem;
text-align: center;
cursor: pointer;
font-size: .79rem;
font-weight: 500;
color: var(--muted);
border-radius: 8px;
transition: color .16s, background .16s;
}
.est-10__ftab:hover {
color: var(--ink);
background: var(--sunk);
}
#est-10-a:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-a"],
#est-10-b:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-b"],
#est-10-c:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-c"] {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.est-10__ftabs:has(#est-10-a:checked) .est-10__ftab[for="est-10-a"],
.est-10__ftabs:has(#est-10-b:checked) .est-10__ftab[for="est-10-b"],
.est-10__ftabs:has(#est-10-c:checked) .est-10__ftab[for="est-10-c"] {
color: var(--ink);
background: var(--sunk);
box-shadow: inset 0 0 0 1px var(--line);
}
.est-10__fpanel {
display: none;
justify-items: center;
text-align: center;
gap: .35rem;
padding: 2rem 1.35rem 1.75rem;
}
.est-10__ftabs:has(#est-10-a:checked) [data-p="a"],
.est-10__ftabs:has(#est-10-b:checked) [data-p="b"],
.est-10__ftabs:has(#est-10-c:checked) [data-p="c"] {
display: grid;
animation: est-10-in .35s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes est-10-in {
from {
opacity: 0;
translate: 0 5px;
}
to {
opacity: 1;
translate: 0 0;
}
}
.est-10__glyph {
position: relative;
inline-size: 3rem;
block-size: 3rem;
display: grid;
place-items: center;
margin-block-end: .5rem;
color: var(--accent);
background: color-mix(in oklab, var(--accent) 8%, var(--surface));
border-radius: 50%;
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 18%, transparent);
}
.est-10__glyph::after {
content: '';
position: absolute;
inset: -.45rem;
border-radius: 50%;
border: 1px solid color-mix(in oklab, var(--accent) 15%, transparent);
animation: est-10-breathe 5s ease-in-out infinite;
}
@keyframes est-10-breathe {
0%,
100% {
opacity: .9;
scale: 1;
}
50% {
opacity: .35;
scale: 1.05;
}
}
.est-10__glyph svg {
inline-size: 1.3rem;
block-size: 1.3rem;
}
.est-10__eh {
margin: 0;
font-size: .95rem;
font-weight: 600;
letter-spacing: -.018em;
}
.est-10__ep {
margin: 0;
max-inline-size: 30ch;
font-size: .82rem;
line-height: 1.5;
color: var(--muted);
text-wrap: pretty;
}
.est-10__ffoot {
padding: .75rem 1rem;
border-block-start: 1px solid var(--line);
background: var(--sunk);
border-end-start-radius: 16px;
border-end-end-radius: 16px;
}
.est-10__ffoot a {
font-size: .8rem;
color: var(--accent);
text-decoration: none;
}
.est-10__ffoot a:hover {
text-decoration: underline;
text-underline-offset: 3px;
}
.est-10__page {
min-inline-size: 0;
padding: clamp(1.25rem,3.5vw,2.5rem) clamp(1rem,3vw,2.25rem);
display: grid;
gap: 1.25rem;
grid-template-columns: repeat(auto-fit,minmax(min(18rem,100%),1fr));
align-content: start;
filter: blur(0);
transition: filter .3s ease, opacity .3s ease;
}
.est-10:has(#est-10-open:checked) .est-10__page {
filter: blur(1.5px);
opacity: .65;
}
.est-10__pcol {
display: grid;
gap: .75rem;
align-content: start;
}
.est-10__block {
background: var(--surface);
border: 1px solid var(--line);
border-radius: 14px;
}
.est-10__block[data-b="head"] {
block-size: 3.25rem;
}
.est-10__block[data-b="chart"] {
block-size: 12rem;
background: linear-gradient(180deg, transparent 55%, color-mix(in oklab, var(--accent) 7%, transparent)), repeating-linear-gradient(180deg, var(--line) 0 1px, transparent 1px 3rem), var(--surface);
}
.est-10__block[data-b="note"] {
block-size: 6rem;
}
.est-10__row {
display: grid;
grid-template-columns: minmax(0,2fr) minmax(0,1fr) minmax(0,1fr);
gap: .75rem;
padding: .9rem 1rem;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 12px;
}
.est-10__row span {
block-size: .6rem;
border-radius: 3px;
background: var(--sunk);
}
.est-10__row span:first-child {
background: color-mix(in oklab, var(--line-2) 75%, transparent);
}
@media (prefers-reduced-motion: reduce) {
.est-10 *,
.est-10 *::before,
.est-10 *::after {
animation: none !important;
transition: none !important;
}
}
@media (forced-colors: active) {
.est-10__flyout,
.est-10__bell,
.est-10__glyph {
border-color: CanvasText;
}
}
```Here's a working CSS Empty State from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Empty Notification Center Flyout
Source: https://codefronts.com/snippets/css-empty-states/empty-notification-center-flyout/
A navbar bell that opens a real notification flyout with zero JavaScript: a single checkbox drives the open state and :has() lifts it to the panel, the bell, and the page behind it, which blurs and dims as the flyout takes focus. Three radio-driven tabs each carry their own empty state.
## HTML
```html
<div class="est-10">
<header class="est-10__bar">
<span class="est-10__brand">Ledgerline</span>
<nav class="est-10__nav">
<a href="#est-10">Accounts</a>
<a href="#est-10">Transfers</a>
<a href="#est-10">Reports</a>
</nav>
<div class="est-10__right">
<input class="est-10__sr" type="checkbox" id="est-10-open" checked>
<label class="est-10__bell" for="est-10-open" title="Notifications">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M6.5 16.5V11a5.5 5.5 0 0 1 11 0v5.5l1.5 2H5z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M10 19.5a2 2 0 0 0 4 0" stroke="currentColor" stroke-width="1.6"/></svg>
<span class="est-10__vh">Notifications</span>
</label>
<span class="est-10__ava" aria-hidden="true">JS</span>
<div class="est-10__flyout" role="dialog" aria-label="Notifications">
<div class="est-10__fhead">
<span class="est-10__ftitle">Notifications</span>
<label class="est-10__fclose" for="est-10-open" aria-label="Close">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M7 7l10 10M17 7 7 17" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>
</label>
</div>
<div class="est-10__ftabs">
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-a" checked>
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-b">
<input class="est-10__sr" type="radio" name="est-10-tab" id="est-10-c">
<div class="est-10__ftabrow">
<label class="est-10__ftab" for="est-10-a">All</label>
<label class="est-10__ftab" for="est-10-b">Approvals</label>
<label class="est-10__ftab" for="est-10-c">Mentions</label>
</div>
<div class="est-10__fpanel" data-p="a">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M5 12.5 9.5 17 19 7.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<p class="est-10__eh">You are up to date</p>
<p class="est-10__ep">Everything since Monday has been read. New activity lands here as it happens.</p>
</div>
<div class="est-10__fpanel" data-p="b">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M12 4.5 19 8v5c0 3.6-2.9 5.9-7 6.9-4.1-1-7-3.3-7-6.9V8z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
</span>
<p class="est-10__eh">No approvals waiting</p>
<p class="est-10__ep">Transfers above your limit appear here for a second signature.</p>
</div>
<div class="est-10__fpanel" data-p="c">
<span class="est-10__glyph" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="M15 8.5a3.5 3.5 0 1 0-3 5.3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="12" cy="12" r="8" stroke="currentColor" stroke-width="1.6"/></svg>
</span>
<p class="est-10__eh">Nobody has tagged you</p>
<p class="est-10__ep">Mentions in comments and reconciliation notes show up here.</p>
</div>
</div>
<div class="est-10__ffoot">
<a href="#est-10">Notification settings</a>
</div>
</div>
</div>
</header>
<main class="est-10__page" aria-hidden="true">
<div class="est-10__pcol">
<div class="est-10__block" data-b="head"></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
<div class="est-10__row"><span></span><span></span><span></span></div>
</div>
<div class="est-10__pcol">
<div class="est-10__block" data-b="chart"></div>
<div class="est-10__block" data-b="note"></div>
</div>
</main>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&display=swap');
.est-10 {
--bg: #f5f6f8;
--surface: #ffffff;
--sunk: #edeff2;
--line: #e3e6ea;
--line-2: #d2d6dc;
--ink: #14161a;
--muted: #5b606a;
--faint: #8a8f99;
--accent: #37507a;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
grid-template-rows: auto minmax(0,1fr);
box-sizing: border-box;
background: var(--bg);
color: var(--ink);
color-scheme: light;
font-synthesis: none;
font-family: 'Geist',ui-sans-serif,system-ui,-apple-system,sans-serif;
-webkit-font-smoothing: antialiased;
}
.est-10 *,
.est-10 *::before,
.est-10 *::after {
box-sizing: border-box;
}
.est-10 [hidden] {
display: none !important;
}
.est-10__vh {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.est-10__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
opacity: 0;
margin: 0;
pointer-events: none;
}
@supports (color: oklch(0.7 0.1 300)) {
.est-10 {
--bg: oklch(0.972 0.004 255);
--surface: oklch(1 0 0);
--sunk: oklch(0.952 0.005 255);
--line: oklch(0.918 0.006 255);
--line-2: oklch(0.865 0.008 255);
--ink: oklch(0.175 0.009 260);
--muted: oklch(0.465 0.012 260);
--faint: oklch(0.625 0.013 260);
--accent: oklch(0.4 0.075 262);
}
}
.est-10__bar {
position: relative;
z-index: 5;
display: flex;
align-items: center;
gap: 1.5rem;
padding: .85rem clamp(1rem,3vw,2.25rem);
border-block-end: 1px solid var(--line);
background: var(--surface);
}
.est-10__brand {
font-size: .95rem;
font-weight: 600;
letter-spacing: -.02em;
}
.est-10__nav {
display: flex;
gap: 1.15rem;
}
.est-10__nav a {
font-size: .86rem;
color: var(--muted);
text-decoration: none;
transition: color .16s;
}
.est-10__nav a:hover {
color: var(--ink);
}
.est-10__right {
position: relative;
margin-inline-start: auto;
display: flex;
align-items: center;
gap: .6rem;
}
.est-10__bell {
position: relative;
inline-size: 2.4rem;
block-size: 2.4rem;
display: grid;
place-items: center;
cursor: pointer;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 10px;
transition: background .16s, border-color .16s;
}
.est-10__bell svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.est-10__bell:hover {
background: var(--sunk);
}
.est-10__right:has(#est-10-open:checked) .est-10__bell {
background: var(--sunk);
border-color: var(--line-2);
}
#est-10-open:focus-visible + .est-10__bell {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.est-10__ava {
inline-size: 2rem;
block-size: 2rem;
display: grid;
place-items: center;
border-radius: 50%;
font-size: .7rem;
font-weight: 600;
color: #fff;
background: var(--accent);
}
.est-10__flyout {
position: absolute;
inset-block-start: calc(100% + .65rem);
inset-inline-end: 0;
inline-size: min(22.5rem,calc(100vw - 2rem));
display: grid;
opacity: 0;
translate: 0 -.4rem;
scale: .985;
pointer-events: none;
transform-origin: 100% 0;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 16px;
box-shadow: 0 1px 2px rgba(18,20,24,.05), 0 30px 60px -34px rgba(18,20,24,.35);
transition: opacity .2s ease, translate .22s cubic-bezier(.2,.7,.3,1), scale .22s cubic-bezier(.2,.7,.3,1);
}
.est-10__flyout::before {
content: '';
position: absolute;
inset-block-start: -.36rem;
inset-inline-end: 1.1rem;
inline-size: .7rem;
block-size: .7rem;
rotate: 45deg;
background: var(--surface);
border-inline-start: 1px solid var(--line);
border-block-start: 1px solid var(--line);
border-start-start-radius: 3px;
}
.est-10__right:has(#est-10-open:checked) .est-10__flyout {
opacity: 1;
translate: 0 0;
scale: 1;
pointer-events: auto;
}
.est-10__fhead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: .85rem 1rem;
border-block-end: 1px solid var(--line);
}
.est-10__ftitle {
font-size: .9rem;
font-weight: 600;
letter-spacing: -.015em;
}
.est-10__fclose {
inline-size: 1.75rem;
block-size: 1.75rem;
display: grid;
place-items: center;
cursor: pointer;
color: var(--faint);
border-radius: 7px;
transition: color .16s, background .16s;
}
.est-10__fclose svg {
inline-size: .85rem;
block-size: .85rem;
}
.est-10__fclose:hover {
color: var(--ink);
background: var(--sunk);
}
.est-10__ftabs {
display: grid;
}
.est-10__ftabrow {
display: flex;
gap: .15rem;
padding: .6rem .7rem 0;
}
.est-10__ftab {
flex: 1 1 auto;
padding: .4rem .5rem;
text-align: center;
cursor: pointer;
font-size: .79rem;
font-weight: 500;
color: var(--muted);
border-radius: 8px;
transition: color .16s, background .16s;
}
.est-10__ftab:hover {
color: var(--ink);
background: var(--sunk);
}
#est-10-a:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-a"],
#est-10-b:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-b"],
#est-10-c:focus-visible ~ .est-10__ftabrow .est-10__ftab[for="est-10-c"] {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.est-10__ftabs:has(#est-10-a:checked) .est-10__ftab[for="est-10-a"],
.est-10__ftabs:has(#est-10-b:checked) .est-10__ftab[for="est-10-b"],
.est-10__ftabs:has(#est-10-c:checked) .est-10__ftab[for="est-10-c"] {
color: var(--ink);
background: var(--sunk);
box-shadow: inset 0 0 0 1px var(--line);
}
.est-10__fpanel {
display: none;
justify-items: center;
text-align: center;
gap: .35rem;
padding: 2rem 1.35rem 1.75rem;
}
.est-10__ftabs:has(#est-10-a:checked) [data-p="a"],
.est-10__ftabs:has(#est-10-b:checked) [data-p="b"],
.est-10__ftabs:has(#est-10-c:checked) [data-p="c"] {
display: grid;
animation: est-10-in .35s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes est-10-in {
from {
opacity: 0;
translate: 0 5px;
}
to {
opacity: 1;
translate: 0 0;
}
}
.est-10__glyph {
position: relative;
inline-size: 3rem;
block-size: 3rem;
display: grid;
place-items: center;
margin-block-end: .5rem;
color: var(--accent);
background: color-mix(in oklab, var(--accent) 8%, var(--surface));
border-radius: 50%;
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 18%, transparent);
}
.est-10__glyph::after {
content: '';
position: absolute;
inset: -.45rem;
border-radius: 50%;
border: 1px solid color-mix(in oklab, var(--accent) 15%, transparent);
animation: est-10-breathe 5s ease-in-out infinite;
}
@keyframes est-10-breathe {
0%,
100% {
opacity: .9;
scale: 1;
}
50% {
opacity: .35;
scale: 1.05;
}
}
.est-10__glyph svg {
inline-size: 1.3rem;
block-size: 1.3rem;
}
.est-10__eh {
margin: 0;
font-size: .95rem;
font-weight: 600;
letter-spacing: -.018em;
}
.est-10__ep {
margin: 0;
max-inline-size: 30ch;
font-size: .82rem;
line-height: 1.5;
color: var(--muted);
text-wrap: pretty;
}
.est-10__ffoot {
padding: .75rem 1rem;
border-block-start: 1px solid var(--line);
background: var(--sunk);
border-end-start-radius: 16px;
border-end-end-radius: 16px;
}
.est-10__ffoot a {
font-size: .8rem;
color: var(--accent);
text-decoration: none;
}
.est-10__ffoot a:hover {
text-decoration: underline;
text-underline-offset: 3px;
}
.est-10__page {
min-inline-size: 0;
padding: clamp(1.25rem,3.5vw,2.5rem) clamp(1rem,3vw,2.25rem);
display: grid;
gap: 1.25rem;
grid-template-columns: repeat(auto-fit,minmax(min(18rem,100%),1fr));
align-content: start;
filter: blur(0);
transition: filter .3s ease, opacity .3s ease;
}
.est-10:has(#est-10-open:checked) .est-10__page {
filter: blur(1.5px);
opacity: .65;
}
.est-10__pcol {
display: grid;
gap: .75rem;
align-content: start;
}
.est-10__block {
background: var(--surface);
border: 1px solid var(--line);
border-radius: 14px;
}
.est-10__block[data-b="head"] {
block-size: 3.25rem;
}
.est-10__block[data-b="chart"] {
block-size: 12rem;
background: linear-gradient(180deg, transparent 55%, color-mix(in oklab, var(--accent) 7%, transparent)), repeating-linear-gradient(180deg, var(--line) 0 1px, transparent 1px 3rem), var(--surface);
}
.est-10__block[data-b="note"] {
block-size: 6rem;
}
.est-10__row {
display: grid;
grid-template-columns: minmax(0,2fr) minmax(0,1fr) minmax(0,1fr);
gap: .75rem;
padding: .9rem 1rem;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 12px;
}
.est-10__row span {
block-size: .6rem;
border-radius: 3px;
background: var(--sunk);
}
.est-10__row span:first-child {
background: color-mix(in oklab, var(--line-2) 75%, transparent);
}
@media (prefers-reduced-motion: reduce) {
.est-10 *,
.est-10 *::before,
.est-10 *::after {
animation: none !important;
transition: none !important;
}
}
@media (forced-colors: active) {
.est-10__flyout,
.est-10__bell,
.est-10__glyph {
border-color: CanvasText;
}
}
```How this works
One checkbox, three consequences. The state lives in a hidden input; :has() reads it wherever it is needed:
.est-10__right:has(#est-10-open:checked) .est-10__flyout{
opacity:1; translate:0 0; scale:1; pointer-events:auto;
}
.est-10__right:has(#est-10-open:checked) .est-10__bell{ background:var(--sunk) }
.est-10:has(#est-10-open:checked) .est-10__page{ filter:blur(1.5px); opacity:.65 }Never animate layout. The panel is always in the DOM and always positioned; only compositor properties change, so opening costs no reflow:
.est-10__flyout{
opacity:0; translate:0 -.4rem; scale:.985;
pointer-events:none; transform-origin:100% 0;
transition:opacity .2s, translate .22s cubic-bezier(.2,.7,.3,1), scale .22s cubic-bezier(.2,.7,.3,1);
}The arrow is a rotated square. Two borders on a small box, turned 45 degrees, sitting half outside the panel — it inherits the panel colour and border so it always matches:
.est-10__flyout::before{
inline-size:.7rem; block-size:.7rem; rotate:45deg;
background:var(--surface);
border-inline-start:1px solid var(--line);
border-block-start:1px solid var(--line);
}Three tabs, three absences. The same radio plus :has() pattern as the outer toggle, and each panel says something specific — up to date, no approvals waiting, nobody has tagged you:
.est-10__ftabs:has(#est-10-b:checked) [data-p="b"]{ display:grid }The demo ships with the flyout open, because the open panel is the state being taught. In production you would start closed.
Make it yours
- Start closed by removing
checkedfrom the toggle input - Rebuild the same panel on the Popover API (
popoverpluspopovertarget) if you can require Chrome 114 / Safari 17 / Firefox 125 — you get light-dismiss, Escape and top-layer stacking for free, and lose the checkbox - Add CSS anchor positioning (
anchor-name/position-anchor) on top of this layout where supported, keeping the absolute position as the fallback - Change the blur behind to a plain dim if the page content is text-heavy
- Add a settings row per tab — empty notification panels are the best place to offer subscription controls
- Swap the three tabs for your real categories; the pattern costs one radio and one rule each
Gotchas — read before shipping
- A checkbox-driven popover has no light-dismiss and no Escape handling. Either accept that, add a full-screen label as a backdrop, or move to the Popover API
- Do not animate
displayorheighton a flyout. Keep it mounted and animate compositor properties, or the navbar shifts on open pointer-events:noneis not optional on a hidden-but-present panel — without it, invisible controls still swallow clicks- The
:has()subject must contain the input. Putting the checkbox outside the wrapper you select on silently breaks everything - An empty notification panel still needs its footer link; the settings path is the second most likely action after closing
filteron the page behind creates a containing block, so fixed children of that subtree will anchor to it
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by :has(), oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 105+.
:has() is required: Chrome 105, Safari 15.4, Firefox 121. Without it the panel stays in its closed transform and the tabs do not switch. color-mix() and oklch() are behind @supports with hex fallbacks; backdrop-filter is not used, and the blur on the page behind is a plain filter.