13 / 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
Permission Denied / Restricted Access State
A 403 that shows the door it is refusing: the document renders behind a blur and a radial scrim, so the user can see the file exists without reading a word of it, and the gate on top names the owners who can approve access. Requesting access swaps the buttons for a confirmation and rewrites the explanation — the screen resolves rather than repeating itself.
Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149
Nucleo · Affiliate link
The code
<div class="est-13">
<div class="est-13__doc" aria-hidden="true">
<div class="est-13__dtop">
<span class="est-13__dbrand">Vellum</span>
<span class="est-13__dpath">Legal / Vendor agreements / Northline MSA</span>
<span class="est-13__dava"></span>
</div>
<div class="est-13__dpage">
<span class="est-13__dh"></span>
<span class="est-13__dl" data-w="92"></span>
<span class="est-13__dl" data-w="84"></span>
<span class="est-13__dl" data-w="96"></span>
<span class="est-13__dl" data-w="61"></span>
<span class="est-13__dh est-13__dh--sm"></span>
<span class="est-13__dl" data-w="88"></span>
<span class="est-13__dl" data-w="79"></span>
<span class="est-13__dl" data-w="93"></span>
<span class="est-13__dl" data-w="48"></span>
</div>
</div>
<div class="est-13__scrim"></div>
<section class="est-13__gate" role="region" aria-labelledby="est-13-h">
<span class="est-13__lock" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><rect x="5" y="10.5" width="14" height="9.5" rx="2.4" stroke="currentColor" stroke-width="1.6"/><path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5" stroke="currentColor" stroke-width="1.6"/><circle cx="12" cy="15.2" r="1.15" fill="currentColor"/></svg>
</span>
<p class="est-13__code">403</p>
<h1 class="est-13__h1" id="est-13-h">This document is restricted</h1>
<p class="est-13__p" id="est-13-p">Northline MSA is limited to the legal group. Your account can see the file exists, but not its contents.</p>
<div class="est-13__acts" id="est-13-acts">
<button class="est-13__pri" type="button" id="est-13-req">Request access</button>
<button class="est-13__ghost" type="button">Ask in the legal channel</button>
</div>
<div class="est-13__pending" id="est-13-pending" hidden>
<span class="est-13__ptick" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="m6 12.5 4 4 8-9" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="est-13__ptext">Request sent to the two document owners</span>
</div>
<dl class="est-13__owners">
<div><dt>Owner</dt><dd>Priya Raman</dd></div>
<div><dt>Owner</dt><dd>Marc Delacroix</dd></div>
<div><dt>Group</dt><dd>legal-core</dd></div>
</dl>
</section>
</div><div class="est-13">
<div class="est-13__doc" aria-hidden="true">
<div class="est-13__dtop">
<span class="est-13__dbrand">Vellum</span>
<span class="est-13__dpath">Legal / Vendor agreements / Northline MSA</span>
<span class="est-13__dava"></span>
</div>
<div class="est-13__dpage">
<span class="est-13__dh"></span>
<span class="est-13__dl" data-w="92"></span>
<span class="est-13__dl" data-w="84"></span>
<span class="est-13__dl" data-w="96"></span>
<span class="est-13__dl" data-w="61"></span>
<span class="est-13__dh est-13__dh--sm"></span>
<span class="est-13__dl" data-w="88"></span>
<span class="est-13__dl" data-w="79"></span>
<span class="est-13__dl" data-w="93"></span>
<span class="est-13__dl" data-w="48"></span>
</div>
</div>
<div class="est-13__scrim"></div>
<section class="est-13__gate" role="region" aria-labelledby="est-13-h">
<span class="est-13__lock" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><rect x="5" y="10.5" width="14" height="9.5" rx="2.4" stroke="currentColor" stroke-width="1.6"/><path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5" stroke="currentColor" stroke-width="1.6"/><circle cx="12" cy="15.2" r="1.15" fill="currentColor"/></svg>
</span>
<p class="est-13__code">403</p>
<h1 class="est-13__h1" id="est-13-h">This document is restricted</h1>
<p class="est-13__p" id="est-13-p">Northline MSA is limited to the legal group. Your account can see the file exists, but not its contents.</p>
<div class="est-13__acts" id="est-13-acts">
<button class="est-13__pri" type="button" id="est-13-req">Request access</button>
<button class="est-13__ghost" type="button">Ask in the legal channel</button>
</div>
<div class="est-13__pending" id="est-13-pending" hidden>
<span class="est-13__ptick" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="m6 12.5 4 4 8-9" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="est-13__ptext">Request sent to the two document owners</span>
</div>
<dl class="est-13__owners">
<div><dt>Owner</dt><dd>Priya Raman</dd></div>
<div><dt>Owner</dt><dd>Marc Delacroix</dd></div>
<div><dt>Group</dt><dd>legal-core</dd></div>
</dl>
</section>
</div>@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500&display=swap');
.est-13 {
--bg: #eff0f2;
--surface: #ffffff;
--sunk: #e7e8ec;
--line: #dfe1e5;
--line-2: #cbced4;
--ink: #15161a;
--muted: #585c65;
--faint: #888c95;
--accent: #2f3a56;
--ok: #2d6b4e;
position: relative;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
box-sizing: border-box;
overflow: hidden;
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-13 *,
.est-13 *::before,
.est-13 *::after {
box-sizing: border-box;
}
.est-13 [hidden] {
display: none !important;
}
@supports (color: oklch(0.7 0.1 300)) {
.est-13 {
--bg: oklch(0.945 0.004 265);
--surface: oklch(1 0 0);
--sunk: oklch(0.925 0.005 265);
--line: oklch(0.902 0.006 265);
--line-2: oklch(0.845 0.008 265);
--ink: oklch(0.175 0.009 265);
--muted: oklch(0.465 0.012 265);
--faint: oklch(0.62 0.012 265);
--accent: oklch(0.33 0.055 265);
--ok: oklch(0.48 0.085 158);
}
}
.est-13__doc {
position: absolute;
inset: 0;
display: grid;
grid-template-rows: auto minmax(0,1fr);
filter: blur(6px) saturate(.85);
transform: scale(1.02);
pointer-events: none;
}
.est-13__dtop {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 1rem clamp(1rem,4vw,3rem);
background: var(--surface);
border-block-end: 1px solid var(--line);
}
.est-13__dbrand {
font-size: .95rem;
font-weight: 600;
}
.est-13__dpath {
font-size: .82rem;
color: var(--faint);
}
.est-13__dava {
margin-inline-start: auto;
inline-size: 1.9rem;
block-size: 1.9rem;
border-radius: 50%;
background: var(--sunk);
}
.est-13__dpage {
inline-size: min(46rem,90%);
justify-self: center;
margin-block: clamp(1.5rem,5vh,3rem);
display: grid;
gap: .85rem;
align-content: start;
padding: clamp(1.75rem,4vw,3rem);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 6px;
box-shadow: 0 30px 60px -50px rgba(20,22,26,.6);
}
.est-13__dh {
block-size: 1.5rem;
inline-size: 52%;
border-radius: 4px;
background: var(--sunk);
}
.est-13__dh--sm {
block-size: 1.1rem;
inline-size: 34%;
margin-block-start: .75rem;
}
.est-13__dl {
block-size: .7rem;
inline-size: calc(var(--w, 90) * 1%);
border-radius: 4px;
background: color-mix(in oklab, var(--sunk) 75%, transparent);
}
.est-13__dl[data-w="92"] {
--w: 92;
}
.est-13__dl[data-w="84"] {
--w: 84;
}
.est-13__dl[data-w="96"] {
--w: 96;
}
.est-13__dl[data-w="61"] {
--w: 61;
}
.est-13__dl[data-w="88"] {
--w: 88;
}
.est-13__dl[data-w="79"] {
--w: 79;
}
.est-13__dl[data-w="93"] {
--w: 93;
}
.est-13__dl[data-w="48"] {
--w: 48;
}
.est-13__scrim {
position: absolute;
inset: 0;
background: radial-gradient(70% 60% at 50% 45%, color-mix(in oklab, var(--bg) 55%, transparent), color-mix(in oklab, var(--bg) 88%, transparent));
}
.est-13__gate {
position: relative;
z-index: 1;
inline-size: min(31rem,calc(100% - 2rem));
margin: clamp(1rem,4vw,2rem);
display: grid;
justify-items: center;
text-align: center;
gap: .5rem;
padding: clamp(1.75rem,4vw,2.5rem);
background: color-mix(in oklab, var(--surface) 96%, transparent);
border: 1px solid var(--line);
border-radius: 22px;
box-shadow: 0 1px 2px rgba(20,22,26,.05), 0 50px 90px -60px rgba(20,22,26,.6);
animation: est-13-in .55s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes est-13-in {
from {
opacity: 0;
translate: 0 12px;
scale: .99;
}
to {
opacity: 1;
translate: 0 0;
scale: 1;
}
}
.est-13__lock {
position: relative;
inline-size: 3.5rem;
block-size: 3.5rem;
display: grid;
place-items: center;
color: var(--accent);
background: color-mix(in oklab, var(--accent) 7%, var(--surface));
border-radius: 16px;
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 15%, transparent);
}
.est-13__lock svg {
inline-size: 1.55rem;
block-size: 1.55rem;
}
.est-13__code {
margin: .75rem 0 0;
font-family: 'Geist Mono',ui-monospace,monospace;
font-size: .72rem;
letter-spacing: .22em;
color: var(--faint);
}
.est-13__h1 {
margin: 0;
font-size: clamp(1.3rem,3vw,1.75rem);
font-weight: 600;
letter-spacing: -.03em;
line-height: 1.15;
text-wrap: balance;
}
.est-13__p {
margin: 0;
max-inline-size: 42ch;
font-size: .92rem;
line-height: 1.6;
color: var(--muted);
text-wrap: pretty;
}
.est-13__acts {
display: flex;
flex-wrap: wrap;
gap: .5rem;
justify-content: center;
margin-block-start: .95rem;
}
.est-13__pri {
min-block-size: 2.8rem;
padding: .68rem 1.1rem;
font: inherit;
font-size: .88rem;
font-weight: 500;
cursor: pointer;
color: #fff;
background: var(--accent);
border: 1px solid transparent;
border-radius: 11px;
box-shadow: 0 10px 22px -16px color-mix(in oklab, var(--accent) 80%, transparent);
transition: translate .16s, background .16s;
}
.est-13__pri:hover {
translate: 0 -1px;
background: color-mix(in oklab, var(--accent) 86%, #000);
}
.est-13__ghost {
min-block-size: 2.8rem;
padding: .68rem 1.05rem;
font: inherit;
font-size: .88rem;
font-weight: 500;
cursor: pointer;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--line-2);
border-radius: 11px;
transition: background .16s;
}
.est-13__ghost:hover {
background: var(--sunk);
}
.est-13__pending {
display: flex;
align-items: center;
gap: .6rem;
margin-block-start: .95rem;
padding: .65rem .85rem;
font-size: .85rem;
color: var(--ok);
background: color-mix(in oklab, var(--ok) 8%, var(--surface));
border: 1px solid color-mix(in oklab, var(--ok) 24%, transparent);
border-radius: 11px;
animation: est-13-in .4s cubic-bezier(.2,.7,.3,1) both;
}
.est-13__ptick {
inline-size: 1.3rem;
block-size: 1.3rem;
display: grid;
place-items: center;
flex: none;
border-radius: 50%;
color: #fff;
background: var(--ok);
}
.est-13__ptick svg {
inline-size: .8rem;
block-size: .8rem;
}
.est-13__owners {
margin: 1.4rem 0 0;
display: flex;
flex-wrap: wrap;
gap: 1.25rem;
justify-content: center;
padding-block-start: 1rem;
border-block-start: 1px solid var(--line);
inline-size: 100%;
}
.est-13__owners div {
display: grid;
gap: .2rem;
}
.est-13__owners dt {
font-family: 'Geist Mono',ui-monospace,monospace;
font-size: .62rem;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--faint);
}
.est-13__owners dd {
margin: 0;
font-size: .84rem;
color: var(--muted);
}
.est-13__pri:focus-visible,
.est-13__ghost:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.est-13 *,
.est-13 *::before,
.est-13 *::after {
animation: none !important;
transition: none !important;
}
}
@media (forced-colors: active) {
.est-13__gate,
.est-13__lock,
.est-13__pri {
border-color: CanvasText;
}
.est-13__doc {
display: none;
}
}@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500&display=swap');
.est-13 {
--bg: #eff0f2;
--surface: #ffffff;
--sunk: #e7e8ec;
--line: #dfe1e5;
--line-2: #cbced4;
--ink: #15161a;
--muted: #585c65;
--faint: #888c95;
--accent: #2f3a56;
--ok: #2d6b4e;
position: relative;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
box-sizing: border-box;
overflow: hidden;
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-13 *,
.est-13 *::before,
.est-13 *::after {
box-sizing: border-box;
}
.est-13 [hidden] {
display: none !important;
}
@supports (color: oklch(0.7 0.1 300)) {
.est-13 {
--bg: oklch(0.945 0.004 265);
--surface: oklch(1 0 0);
--sunk: oklch(0.925 0.005 265);
--line: oklch(0.902 0.006 265);
--line-2: oklch(0.845 0.008 265);
--ink: oklch(0.175 0.009 265);
--muted: oklch(0.465 0.012 265);
--faint: oklch(0.62 0.012 265);
--accent: oklch(0.33 0.055 265);
--ok: oklch(0.48 0.085 158);
}
}
.est-13__doc {
position: absolute;
inset: 0;
display: grid;
grid-template-rows: auto minmax(0,1fr);
filter: blur(6px) saturate(.85);
transform: scale(1.02);
pointer-events: none;
}
.est-13__dtop {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 1rem clamp(1rem,4vw,3rem);
background: var(--surface);
border-block-end: 1px solid var(--line);
}
.est-13__dbrand {
font-size: .95rem;
font-weight: 600;
}
.est-13__dpath {
font-size: .82rem;
color: var(--faint);
}
.est-13__dava {
margin-inline-start: auto;
inline-size: 1.9rem;
block-size: 1.9rem;
border-radius: 50%;
background: var(--sunk);
}
.est-13__dpage {
inline-size: min(46rem,90%);
justify-self: center;
margin-block: clamp(1.5rem,5vh,3rem);
display: grid;
gap: .85rem;
align-content: start;
padding: clamp(1.75rem,4vw,3rem);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 6px;
box-shadow: 0 30px 60px -50px rgba(20,22,26,.6);
}
.est-13__dh {
block-size: 1.5rem;
inline-size: 52%;
border-radius: 4px;
background: var(--sunk);
}
.est-13__dh--sm {
block-size: 1.1rem;
inline-size: 34%;
margin-block-start: .75rem;
}
.est-13__dl {
block-size: .7rem;
inline-size: calc(var(--w, 90) * 1%);
border-radius: 4px;
background: color-mix(in oklab, var(--sunk) 75%, transparent);
}
.est-13__dl[data-w="92"] {
--w: 92;
}
.est-13__dl[data-w="84"] {
--w: 84;
}
.est-13__dl[data-w="96"] {
--w: 96;
}
.est-13__dl[data-w="61"] {
--w: 61;
}
.est-13__dl[data-w="88"] {
--w: 88;
}
.est-13__dl[data-w="79"] {
--w: 79;
}
.est-13__dl[data-w="93"] {
--w: 93;
}
.est-13__dl[data-w="48"] {
--w: 48;
}
.est-13__scrim {
position: absolute;
inset: 0;
background: radial-gradient(70% 60% at 50% 45%, color-mix(in oklab, var(--bg) 55%, transparent), color-mix(in oklab, var(--bg) 88%, transparent));
}
.est-13__gate {
position: relative;
z-index: 1;
inline-size: min(31rem,calc(100% - 2rem));
margin: clamp(1rem,4vw,2rem);
display: grid;
justify-items: center;
text-align: center;
gap: .5rem;
padding: clamp(1.75rem,4vw,2.5rem);
background: color-mix(in oklab, var(--surface) 96%, transparent);
border: 1px solid var(--line);
border-radius: 22px;
box-shadow: 0 1px 2px rgba(20,22,26,.05), 0 50px 90px -60px rgba(20,22,26,.6);
animation: est-13-in .55s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes est-13-in {
from {
opacity: 0;
translate: 0 12px;
scale: .99;
}
to {
opacity: 1;
translate: 0 0;
scale: 1;
}
}
.est-13__lock {
position: relative;
inline-size: 3.5rem;
block-size: 3.5rem;
display: grid;
place-items: center;
color: var(--accent);
background: color-mix(in oklab, var(--accent) 7%, var(--surface));
border-radius: 16px;
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 15%, transparent);
}
.est-13__lock svg {
inline-size: 1.55rem;
block-size: 1.55rem;
}
.est-13__code {
margin: .75rem 0 0;
font-family: 'Geist Mono',ui-monospace,monospace;
font-size: .72rem;
letter-spacing: .22em;
color: var(--faint);
}
.est-13__h1 {
margin: 0;
font-size: clamp(1.3rem,3vw,1.75rem);
font-weight: 600;
letter-spacing: -.03em;
line-height: 1.15;
text-wrap: balance;
}
.est-13__p {
margin: 0;
max-inline-size: 42ch;
font-size: .92rem;
line-height: 1.6;
color: var(--muted);
text-wrap: pretty;
}
.est-13__acts {
display: flex;
flex-wrap: wrap;
gap: .5rem;
justify-content: center;
margin-block-start: .95rem;
}
.est-13__pri {
min-block-size: 2.8rem;
padding: .68rem 1.1rem;
font: inherit;
font-size: .88rem;
font-weight: 500;
cursor: pointer;
color: #fff;
background: var(--accent);
border: 1px solid transparent;
border-radius: 11px;
box-shadow: 0 10px 22px -16px color-mix(in oklab, var(--accent) 80%, transparent);
transition: translate .16s, background .16s;
}
.est-13__pri:hover {
translate: 0 -1px;
background: color-mix(in oklab, var(--accent) 86%, #000);
}
.est-13__ghost {
min-block-size: 2.8rem;
padding: .68rem 1.05rem;
font: inherit;
font-size: .88rem;
font-weight: 500;
cursor: pointer;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--line-2);
border-radius: 11px;
transition: background .16s;
}
.est-13__ghost:hover {
background: var(--sunk);
}
.est-13__pending {
display: flex;
align-items: center;
gap: .6rem;
margin-block-start: .95rem;
padding: .65rem .85rem;
font-size: .85rem;
color: var(--ok);
background: color-mix(in oklab, var(--ok) 8%, var(--surface));
border: 1px solid color-mix(in oklab, var(--ok) 24%, transparent);
border-radius: 11px;
animation: est-13-in .4s cubic-bezier(.2,.7,.3,1) both;
}
.est-13__ptick {
inline-size: 1.3rem;
block-size: 1.3rem;
display: grid;
place-items: center;
flex: none;
border-radius: 50%;
color: #fff;
background: var(--ok);
}
.est-13__ptick svg {
inline-size: .8rem;
block-size: .8rem;
}
.est-13__owners {
margin: 1.4rem 0 0;
display: flex;
flex-wrap: wrap;
gap: 1.25rem;
justify-content: center;
padding-block-start: 1rem;
border-block-start: 1px solid var(--line);
inline-size: 100%;
}
.est-13__owners div {
display: grid;
gap: .2rem;
}
.est-13__owners dt {
font-family: 'Geist Mono',ui-monospace,monospace;
font-size: .62rem;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--faint);
}
.est-13__owners dd {
margin: 0;
font-size: .84rem;
color: var(--muted);
}
.est-13__pri:focus-visible,
.est-13__ghost:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.est-13 *,
.est-13 *::before,
.est-13 *::after {
animation: none !important;
transition: none !important;
}
}
@media (forced-colors: active) {
.est-13__gate,
.est-13__lock,
.est-13__pri {
border-color: CanvasText;
}
.est-13__doc {
display: none;
}
}(function(){
var root = document.querySelector('.est-13'); if(!root) return;
var req = root.querySelector('#est-13-req'), acts = root.querySelector('#est-13-acts'),
pending = root.querySelector('#est-13-pending'), p = root.querySelector('#est-13-p');
req.addEventListener('click', function(){
acts.hidden = true; pending.hidden = false;
p.textContent = 'Priya or Marc can approve it. You will get a notification the moment one of them does.';
});
})();(function(){
var root = document.querySelector('.est-13'); if(!root) return;
var req = root.querySelector('#est-13-req'), acts = root.querySelector('#est-13-acts'),
pending = root.querySelector('#est-13-pending'), p = root.querySelector('#est-13-p');
req.addEventListener('click', function(){
acts.hidden = true; pending.hidden = false;
p.textContent = 'Priya or Marc can approve it. You will get a notification the moment one of them does.';
});
})();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: Permission Denied / Restricted Access State
Source: https://codefronts.com/snippets/css-empty-states/permission-denied-restricted-access-state/
A 403 that shows the door it is refusing: the document renders behind a blur and a radial scrim, so the user can see the file exists without reading a word of it, and the gate on top names the owners who can approve access. Requesting access swaps the buttons for a confirmation and rewrites the explanation — the screen resolves rather than repeating itself.
## HTML
```html
<div class="est-13">
<div class="est-13__doc" aria-hidden="true">
<div class="est-13__dtop">
<span class="est-13__dbrand">Vellum</span>
<span class="est-13__dpath">Legal / Vendor agreements / Northline MSA</span>
<span class="est-13__dava"></span>
</div>
<div class="est-13__dpage">
<span class="est-13__dh"></span>
<span class="est-13__dl" data-w="92"></span>
<span class="est-13__dl" data-w="84"></span>
<span class="est-13__dl" data-w="96"></span>
<span class="est-13__dl" data-w="61"></span>
<span class="est-13__dh est-13__dh--sm"></span>
<span class="est-13__dl" data-w="88"></span>
<span class="est-13__dl" data-w="79"></span>
<span class="est-13__dl" data-w="93"></span>
<span class="est-13__dl" data-w="48"></span>
</div>
</div>
<div class="est-13__scrim"></div>
<section class="est-13__gate" role="region" aria-labelledby="est-13-h">
<span class="est-13__lock" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><rect x="5" y="10.5" width="14" height="9.5" rx="2.4" stroke="currentColor" stroke-width="1.6"/><path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5" stroke="currentColor" stroke-width="1.6"/><circle cx="12" cy="15.2" r="1.15" fill="currentColor"/></svg>
</span>
<p class="est-13__code">403</p>
<h1 class="est-13__h1" id="est-13-h">This document is restricted</h1>
<p class="est-13__p" id="est-13-p">Northline MSA is limited to the legal group. Your account can see the file exists, but not its contents.</p>
<div class="est-13__acts" id="est-13-acts">
<button class="est-13__pri" type="button" id="est-13-req">Request access</button>
<button class="est-13__ghost" type="button">Ask in the legal channel</button>
</div>
<div class="est-13__pending" id="est-13-pending" hidden>
<span class="est-13__ptick" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="m6 12.5 4 4 8-9" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="est-13__ptext">Request sent to the two document owners</span>
</div>
<dl class="est-13__owners">
<div><dt>Owner</dt><dd>Priya Raman</dd></div>
<div><dt>Owner</dt><dd>Marc Delacroix</dd></div>
<div><dt>Group</dt><dd>legal-core</dd></div>
</dl>
</section>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500&display=swap');
.est-13 {
--bg: #eff0f2;
--surface: #ffffff;
--sunk: #e7e8ec;
--line: #dfe1e5;
--line-2: #cbced4;
--ink: #15161a;
--muted: #585c65;
--faint: #888c95;
--accent: #2f3a56;
--ok: #2d6b4e;
position: relative;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
box-sizing: border-box;
overflow: hidden;
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-13 *,
.est-13 *::before,
.est-13 *::after {
box-sizing: border-box;
}
.est-13 [hidden] {
display: none !important;
}
@supports (color: oklch(0.7 0.1 300)) {
.est-13 {
--bg: oklch(0.945 0.004 265);
--surface: oklch(1 0 0);
--sunk: oklch(0.925 0.005 265);
--line: oklch(0.902 0.006 265);
--line-2: oklch(0.845 0.008 265);
--ink: oklch(0.175 0.009 265);
--muted: oklch(0.465 0.012 265);
--faint: oklch(0.62 0.012 265);
--accent: oklch(0.33 0.055 265);
--ok: oklch(0.48 0.085 158);
}
}
.est-13__doc {
position: absolute;
inset: 0;
display: grid;
grid-template-rows: auto minmax(0,1fr);
filter: blur(6px) saturate(.85);
transform: scale(1.02);
pointer-events: none;
}
.est-13__dtop {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 1rem clamp(1rem,4vw,3rem);
background: var(--surface);
border-block-end: 1px solid var(--line);
}
.est-13__dbrand {
font-size: .95rem;
font-weight: 600;
}
.est-13__dpath {
font-size: .82rem;
color: var(--faint);
}
.est-13__dava {
margin-inline-start: auto;
inline-size: 1.9rem;
block-size: 1.9rem;
border-radius: 50%;
background: var(--sunk);
}
.est-13__dpage {
inline-size: min(46rem,90%);
justify-self: center;
margin-block: clamp(1.5rem,5vh,3rem);
display: grid;
gap: .85rem;
align-content: start;
padding: clamp(1.75rem,4vw,3rem);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 6px;
box-shadow: 0 30px 60px -50px rgba(20,22,26,.6);
}
.est-13__dh {
block-size: 1.5rem;
inline-size: 52%;
border-radius: 4px;
background: var(--sunk);
}
.est-13__dh--sm {
block-size: 1.1rem;
inline-size: 34%;
margin-block-start: .75rem;
}
.est-13__dl {
block-size: .7rem;
inline-size: calc(var(--w, 90) * 1%);
border-radius: 4px;
background: color-mix(in oklab, var(--sunk) 75%, transparent);
}
.est-13__dl[data-w="92"] {
--w: 92;
}
.est-13__dl[data-w="84"] {
--w: 84;
}
.est-13__dl[data-w="96"] {
--w: 96;
}
.est-13__dl[data-w="61"] {
--w: 61;
}
.est-13__dl[data-w="88"] {
--w: 88;
}
.est-13__dl[data-w="79"] {
--w: 79;
}
.est-13__dl[data-w="93"] {
--w: 93;
}
.est-13__dl[data-w="48"] {
--w: 48;
}
.est-13__scrim {
position: absolute;
inset: 0;
background: radial-gradient(70% 60% at 50% 45%, color-mix(in oklab, var(--bg) 55%, transparent), color-mix(in oklab, var(--bg) 88%, transparent));
}
.est-13__gate {
position: relative;
z-index: 1;
inline-size: min(31rem,calc(100% - 2rem));
margin: clamp(1rem,4vw,2rem);
display: grid;
justify-items: center;
text-align: center;
gap: .5rem;
padding: clamp(1.75rem,4vw,2.5rem);
background: color-mix(in oklab, var(--surface) 96%, transparent);
border: 1px solid var(--line);
border-radius: 22px;
box-shadow: 0 1px 2px rgba(20,22,26,.05), 0 50px 90px -60px rgba(20,22,26,.6);
animation: est-13-in .55s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes est-13-in {
from {
opacity: 0;
translate: 0 12px;
scale: .99;
}
to {
opacity: 1;
translate: 0 0;
scale: 1;
}
}
.est-13__lock {
position: relative;
inline-size: 3.5rem;
block-size: 3.5rem;
display: grid;
place-items: center;
color: var(--accent);
background: color-mix(in oklab, var(--accent) 7%, var(--surface));
border-radius: 16px;
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 15%, transparent);
}
.est-13__lock svg {
inline-size: 1.55rem;
block-size: 1.55rem;
}
.est-13__code {
margin: .75rem 0 0;
font-family: 'Geist Mono',ui-monospace,monospace;
font-size: .72rem;
letter-spacing: .22em;
color: var(--faint);
}
.est-13__h1 {
margin: 0;
font-size: clamp(1.3rem,3vw,1.75rem);
font-weight: 600;
letter-spacing: -.03em;
line-height: 1.15;
text-wrap: balance;
}
.est-13__p {
margin: 0;
max-inline-size: 42ch;
font-size: .92rem;
line-height: 1.6;
color: var(--muted);
text-wrap: pretty;
}
.est-13__acts {
display: flex;
flex-wrap: wrap;
gap: .5rem;
justify-content: center;
margin-block-start: .95rem;
}
.est-13__pri {
min-block-size: 2.8rem;
padding: .68rem 1.1rem;
font: inherit;
font-size: .88rem;
font-weight: 500;
cursor: pointer;
color: #fff;
background: var(--accent);
border: 1px solid transparent;
border-radius: 11px;
box-shadow: 0 10px 22px -16px color-mix(in oklab, var(--accent) 80%, transparent);
transition: translate .16s, background .16s;
}
.est-13__pri:hover {
translate: 0 -1px;
background: color-mix(in oklab, var(--accent) 86%, #000);
}
.est-13__ghost {
min-block-size: 2.8rem;
padding: .68rem 1.05rem;
font: inherit;
font-size: .88rem;
font-weight: 500;
cursor: pointer;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--line-2);
border-radius: 11px;
transition: background .16s;
}
.est-13__ghost:hover {
background: var(--sunk);
}
.est-13__pending {
display: flex;
align-items: center;
gap: .6rem;
margin-block-start: .95rem;
padding: .65rem .85rem;
font-size: .85rem;
color: var(--ok);
background: color-mix(in oklab, var(--ok) 8%, var(--surface));
border: 1px solid color-mix(in oklab, var(--ok) 24%, transparent);
border-radius: 11px;
animation: est-13-in .4s cubic-bezier(.2,.7,.3,1) both;
}
.est-13__ptick {
inline-size: 1.3rem;
block-size: 1.3rem;
display: grid;
place-items: center;
flex: none;
border-radius: 50%;
color: #fff;
background: var(--ok);
}
.est-13__ptick svg {
inline-size: .8rem;
block-size: .8rem;
}
.est-13__owners {
margin: 1.4rem 0 0;
display: flex;
flex-wrap: wrap;
gap: 1.25rem;
justify-content: center;
padding-block-start: 1rem;
border-block-start: 1px solid var(--line);
inline-size: 100%;
}
.est-13__owners div {
display: grid;
gap: .2rem;
}
.est-13__owners dt {
font-family: 'Geist Mono',ui-monospace,monospace;
font-size: .62rem;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--faint);
}
.est-13__owners dd {
margin: 0;
font-size: .84rem;
color: var(--muted);
}
.est-13__pri:focus-visible,
.est-13__ghost:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.est-13 *,
.est-13 *::before,
.est-13 *::after {
animation: none !important;
transition: none !important;
}
}
@media (forced-colors: active) {
.est-13__gate,
.est-13__lock,
.est-13__pri {
border-color: CanvasText;
}
.est-13__doc {
display: none;
}
}
```
## JavaScript
```js
(function(){
var root = document.querySelector('.est-13'); if(!root) return;
var req = root.querySelector('#est-13-req'), acts = root.querySelector('#est-13-acts'),
pending = root.querySelector('#est-13-pending'), p = root.querySelector('#est-13-p');
req.addEventListener('click', function(){
acts.hidden = true; pending.hidden = false;
p.textContent = 'Priya or Marc can approve it. You will get a notification the moment one of them does.';
});
})();
```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: Permission Denied / Restricted Access State
Source: https://codefronts.com/snippets/css-empty-states/permission-denied-restricted-access-state/
A 403 that shows the door it is refusing: the document renders behind a blur and a radial scrim, so the user can see the file exists without reading a word of it, and the gate on top names the owners who can approve access. Requesting access swaps the buttons for a confirmation and rewrites the explanation — the screen resolves rather than repeating itself.
## HTML
```html
<div class="est-13">
<div class="est-13__doc" aria-hidden="true">
<div class="est-13__dtop">
<span class="est-13__dbrand">Vellum</span>
<span class="est-13__dpath">Legal / Vendor agreements / Northline MSA</span>
<span class="est-13__dava"></span>
</div>
<div class="est-13__dpage">
<span class="est-13__dh"></span>
<span class="est-13__dl" data-w="92"></span>
<span class="est-13__dl" data-w="84"></span>
<span class="est-13__dl" data-w="96"></span>
<span class="est-13__dl" data-w="61"></span>
<span class="est-13__dh est-13__dh--sm"></span>
<span class="est-13__dl" data-w="88"></span>
<span class="est-13__dl" data-w="79"></span>
<span class="est-13__dl" data-w="93"></span>
<span class="est-13__dl" data-w="48"></span>
</div>
</div>
<div class="est-13__scrim"></div>
<section class="est-13__gate" role="region" aria-labelledby="est-13-h">
<span class="est-13__lock" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><rect x="5" y="10.5" width="14" height="9.5" rx="2.4" stroke="currentColor" stroke-width="1.6"/><path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5" stroke="currentColor" stroke-width="1.6"/><circle cx="12" cy="15.2" r="1.15" fill="currentColor"/></svg>
</span>
<p class="est-13__code">403</p>
<h1 class="est-13__h1" id="est-13-h">This document is restricted</h1>
<p class="est-13__p" id="est-13-p">Northline MSA is limited to the legal group. Your account can see the file exists, but not its contents.</p>
<div class="est-13__acts" id="est-13-acts">
<button class="est-13__pri" type="button" id="est-13-req">Request access</button>
<button class="est-13__ghost" type="button">Ask in the legal channel</button>
</div>
<div class="est-13__pending" id="est-13-pending" hidden>
<span class="est-13__ptick" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" focusable="false"><path d="m6 12.5 4 4 8-9" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
<span class="est-13__ptext">Request sent to the two document owners</span>
</div>
<dl class="est-13__owners">
<div><dt>Owner</dt><dd>Priya Raman</dd></div>
<div><dt>Owner</dt><dd>Marc Delacroix</dd></div>
<div><dt>Group</dt><dd>legal-core</dd></div>
</dl>
</section>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500&display=swap');
.est-13 {
--bg: #eff0f2;
--surface: #ffffff;
--sunk: #e7e8ec;
--line: #dfe1e5;
--line-2: #cbced4;
--ink: #15161a;
--muted: #585c65;
--faint: #888c95;
--accent: #2f3a56;
--ok: #2d6b4e;
position: relative;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
box-sizing: border-box;
overflow: hidden;
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-13 *,
.est-13 *::before,
.est-13 *::after {
box-sizing: border-box;
}
.est-13 [hidden] {
display: none !important;
}
@supports (color: oklch(0.7 0.1 300)) {
.est-13 {
--bg: oklch(0.945 0.004 265);
--surface: oklch(1 0 0);
--sunk: oklch(0.925 0.005 265);
--line: oklch(0.902 0.006 265);
--line-2: oklch(0.845 0.008 265);
--ink: oklch(0.175 0.009 265);
--muted: oklch(0.465 0.012 265);
--faint: oklch(0.62 0.012 265);
--accent: oklch(0.33 0.055 265);
--ok: oklch(0.48 0.085 158);
}
}
.est-13__doc {
position: absolute;
inset: 0;
display: grid;
grid-template-rows: auto minmax(0,1fr);
filter: blur(6px) saturate(.85);
transform: scale(1.02);
pointer-events: none;
}
.est-13__dtop {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 1rem clamp(1rem,4vw,3rem);
background: var(--surface);
border-block-end: 1px solid var(--line);
}
.est-13__dbrand {
font-size: .95rem;
font-weight: 600;
}
.est-13__dpath {
font-size: .82rem;
color: var(--faint);
}
.est-13__dava {
margin-inline-start: auto;
inline-size: 1.9rem;
block-size: 1.9rem;
border-radius: 50%;
background: var(--sunk);
}
.est-13__dpage {
inline-size: min(46rem,90%);
justify-self: center;
margin-block: clamp(1.5rem,5vh,3rem);
display: grid;
gap: .85rem;
align-content: start;
padding: clamp(1.75rem,4vw,3rem);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 6px;
box-shadow: 0 30px 60px -50px rgba(20,22,26,.6);
}
.est-13__dh {
block-size: 1.5rem;
inline-size: 52%;
border-radius: 4px;
background: var(--sunk);
}
.est-13__dh--sm {
block-size: 1.1rem;
inline-size: 34%;
margin-block-start: .75rem;
}
.est-13__dl {
block-size: .7rem;
inline-size: calc(var(--w, 90) * 1%);
border-radius: 4px;
background: color-mix(in oklab, var(--sunk) 75%, transparent);
}
.est-13__dl[data-w="92"] {
--w: 92;
}
.est-13__dl[data-w="84"] {
--w: 84;
}
.est-13__dl[data-w="96"] {
--w: 96;
}
.est-13__dl[data-w="61"] {
--w: 61;
}
.est-13__dl[data-w="88"] {
--w: 88;
}
.est-13__dl[data-w="79"] {
--w: 79;
}
.est-13__dl[data-w="93"] {
--w: 93;
}
.est-13__dl[data-w="48"] {
--w: 48;
}
.est-13__scrim {
position: absolute;
inset: 0;
background: radial-gradient(70% 60% at 50% 45%, color-mix(in oklab, var(--bg) 55%, transparent), color-mix(in oklab, var(--bg) 88%, transparent));
}
.est-13__gate {
position: relative;
z-index: 1;
inline-size: min(31rem,calc(100% - 2rem));
margin: clamp(1rem,4vw,2rem);
display: grid;
justify-items: center;
text-align: center;
gap: .5rem;
padding: clamp(1.75rem,4vw,2.5rem);
background: color-mix(in oklab, var(--surface) 96%, transparent);
border: 1px solid var(--line);
border-radius: 22px;
box-shadow: 0 1px 2px rgba(20,22,26,.05), 0 50px 90px -60px rgba(20,22,26,.6);
animation: est-13-in .55s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes est-13-in {
from {
opacity: 0;
translate: 0 12px;
scale: .99;
}
to {
opacity: 1;
translate: 0 0;
scale: 1;
}
}
.est-13__lock {
position: relative;
inline-size: 3.5rem;
block-size: 3.5rem;
display: grid;
place-items: center;
color: var(--accent);
background: color-mix(in oklab, var(--accent) 7%, var(--surface));
border-radius: 16px;
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 15%, transparent);
}
.est-13__lock svg {
inline-size: 1.55rem;
block-size: 1.55rem;
}
.est-13__code {
margin: .75rem 0 0;
font-family: 'Geist Mono',ui-monospace,monospace;
font-size: .72rem;
letter-spacing: .22em;
color: var(--faint);
}
.est-13__h1 {
margin: 0;
font-size: clamp(1.3rem,3vw,1.75rem);
font-weight: 600;
letter-spacing: -.03em;
line-height: 1.15;
text-wrap: balance;
}
.est-13__p {
margin: 0;
max-inline-size: 42ch;
font-size: .92rem;
line-height: 1.6;
color: var(--muted);
text-wrap: pretty;
}
.est-13__acts {
display: flex;
flex-wrap: wrap;
gap: .5rem;
justify-content: center;
margin-block-start: .95rem;
}
.est-13__pri {
min-block-size: 2.8rem;
padding: .68rem 1.1rem;
font: inherit;
font-size: .88rem;
font-weight: 500;
cursor: pointer;
color: #fff;
background: var(--accent);
border: 1px solid transparent;
border-radius: 11px;
box-shadow: 0 10px 22px -16px color-mix(in oklab, var(--accent) 80%, transparent);
transition: translate .16s, background .16s;
}
.est-13__pri:hover {
translate: 0 -1px;
background: color-mix(in oklab, var(--accent) 86%, #000);
}
.est-13__ghost {
min-block-size: 2.8rem;
padding: .68rem 1.05rem;
font: inherit;
font-size: .88rem;
font-weight: 500;
cursor: pointer;
color: var(--ink);
background: var(--surface);
border: 1px solid var(--line-2);
border-radius: 11px;
transition: background .16s;
}
.est-13__ghost:hover {
background: var(--sunk);
}
.est-13__pending {
display: flex;
align-items: center;
gap: .6rem;
margin-block-start: .95rem;
padding: .65rem .85rem;
font-size: .85rem;
color: var(--ok);
background: color-mix(in oklab, var(--ok) 8%, var(--surface));
border: 1px solid color-mix(in oklab, var(--ok) 24%, transparent);
border-radius: 11px;
animation: est-13-in .4s cubic-bezier(.2,.7,.3,1) both;
}
.est-13__ptick {
inline-size: 1.3rem;
block-size: 1.3rem;
display: grid;
place-items: center;
flex: none;
border-radius: 50%;
color: #fff;
background: var(--ok);
}
.est-13__ptick svg {
inline-size: .8rem;
block-size: .8rem;
}
.est-13__owners {
margin: 1.4rem 0 0;
display: flex;
flex-wrap: wrap;
gap: 1.25rem;
justify-content: center;
padding-block-start: 1rem;
border-block-start: 1px solid var(--line);
inline-size: 100%;
}
.est-13__owners div {
display: grid;
gap: .2rem;
}
.est-13__owners dt {
font-family: 'Geist Mono',ui-monospace,monospace;
font-size: .62rem;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--faint);
}
.est-13__owners dd {
margin: 0;
font-size: .84rem;
color: var(--muted);
}
.est-13__pri:focus-visible,
.est-13__ghost:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.est-13 *,
.est-13 *::before,
.est-13 *::after {
animation: none !important;
transition: none !important;
}
}
@media (forced-colors: active) {
.est-13__gate,
.est-13__lock,
.est-13__pri {
border-color: CanvasText;
}
.est-13__doc {
display: none;
}
}
```
## JavaScript
```js
(function(){
var root = document.querySelector('.est-13'); if(!root) return;
var req = root.querySelector('#est-13-req'), acts = root.querySelector('#est-13-acts'),
pending = root.querySelector('#est-13-pending'), p = root.querySelector('#est-13-p');
req.addEventListener('click', function(){
acts.hidden = true; pending.hidden = false;
p.textContent = 'Priya or Marc can approve it. You will get a notification the moment one of them does.';
});
})();
```How this works
Blur the content, do not delete it. Seeing the shape of a document is what makes a 403 feel like a permission problem rather than a broken link:
.est-13__doc{ position:absolute; inset:0; filter:blur(6px) saturate(.85); transform:scale(1.02) }
.est-13__scrim{ position:absolute; inset:0;
background:radial-gradient(70% 60% at 50% 45%, color-mix(in oklab, var(--bg) 55%, transparent),
color-mix(in oklab, var(--bg) 88%, transparent)) }The scale(1.02) matters: a blurred element has soft edges, and scaling it slightly pushes those edges past the viewport.
Skeleton widths as data. One custom property, one attribute per line:
.est-13__dl{ inline-size:calc(var(--w, 90) * 1%) }
.est-13__dl[data-w="61"]{ --w:61 }Name the approvers. The most useful thing on a permission wall is who can grant it:
<dl class="est-13__owners">
<div><dt>Owner</dt><dd>Priya Raman</dd></div>
<div><dt>Group</dt><dd>legal-core</dd></div>
</dl>Resolve the request. One click, three changes — actions out, confirmation in, explanation rewritten to say what happens next:
acts.hidden = true; pending.hidden = false;
p.textContent = 'Priya or Marc can approve it…';Make it yours
- Pull the real owner list from your ACL — two names and a group is usually enough
- Add a message field to the request so approvers get context instead of a bare notification
- If the document title itself is sensitive, blur the header too and drop the breadcrumb
- Show the requester their pending state on return visits rather than offering the button again
- For a group-managed resource, link the group directly instead of naming individuals
- Swap the blurred skeleton for a genuine redacted preview if your product supports partial access
Gotchas — read before shipping
- Blurred text is not secure. Never blur real content client-side — render skeleton geometry, as this demo does
- A blurred element needs a slight scale, or its soft edges reveal the page background at the seams
- Do not use 403 copy that implies wrongdoing. The user followed a link; the answer is who to ask
filtercreates a containing block, so any fixed-position child of the document layer will anchor to it- Request access must be idempotent. A second click should say already requested, not send another notification
- In forced-colors mode a decorative blurred layer becomes noise — hide it explicitly
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
filter: blur() is Baseline relevant. color-mix() and oklch() sit behind @supports with hex fallbacks, and the whole decorative layer is removed under forced-colors: active.