Weekend in Lisbon
Three days of tiles, tarts and trams.
Three notifications stacked in depth, the older ones sunk back and peeking out below. On hover they come forward and spread into a list. Stacking uses translateZ, so the depth is real perspective.
Also known as: stacked notifications expand, ios notification stack css, card stack spread.
Lunch at one? I found a place.
Design review in 15 minutes
main is live on production
<div class="hfx-notification-stack-3d">
<div class="hfx-notification-stack-3d-card" style="--i: 0">
<span class="hfx-notification-stack-3d-icon">M</span>
<div><strong>Maya Chen</strong><p>Lunch at one? I found a place.</p></div>
<time>now</time>
</div>
<div class="hfx-notification-stack-3d-card" style="--i: 1">
<span class="hfx-notification-stack-3d-icon">C</span>
<div><strong>Calendar</strong><p>Design review in 15 minutes</p></div>
<time>5m</time>
</div>
<div class="hfx-notification-stack-3d-card" style="--i: 2">
<span class="hfx-notification-stack-3d-icon">D</span>
<div><strong>Deploys</strong><p>main is live on production</p></div>
<time>1h</time>
</div>
</div>.hfx-notification-stack-3d {
--c: var(--hfx-color, #3b82f6);
font-size: var(--hfx-size, 16px);
position: relative;
width: 16em;
height: 11em;
perspective: 30em;
font-family: system-ui, sans-serif;
}
.hfx-notification-stack-3d-card {
position: absolute;
top: 50%;
left: 0;
right: 0;
z-index: calc(3 - var(--i));
display: flex;
align-items: center;
gap: 0.7em;
height: 3.3em;
margin-top: -1.65em;
padding: 0 0.9em;
border: 1px solid color-mix(in srgb, var(--c) 15%, transparent);
border-radius: 0.9em;
background: #fff;
box-shadow: 0 0.4em 1em color-mix(in srgb, #000 10%, transparent);
color: #111827;
opacity: calc(1 - var(--i) * 0.2);
transform: translateY(calc(var(--i) * 0.6em)) translateZ(calc(var(--i) * -1.4em));
transition: transform 0.55s cubic-bezier(0.3, 1.3, 0.5, 1), opacity 0.3s ease;
transition-delay: calc(var(--i) * 0.04s);
}
.hfx-notification-stack-3d-icon {
display: grid;
flex: none;
place-items: center;
width: 2em;
height: 2em;
border-radius: 0.55em;
background: var(--c);
color: #fff;
font-size: 0.9em;
font-weight: 700;
}
.hfx-notification-stack-3d-card div {
flex: 1;
min-width: 0;
}
.hfx-notification-stack-3d-card strong {
display: block;
font-size: 0.8em;
}
.hfx-notification-stack-3d-card p {
margin: 0;
overflow: hidden;
font-size: 0.72em;
color: #6b7280;
white-space: nowrap;
text-overflow: ellipsis;
}
.hfx-notification-stack-3d-card time {
align-self: flex-start;
margin-top: 0.9em;
font-size: 0.65em;
color: #9ca3af;
}
.hfx-notification-stack-3d:hover .hfx-notification-stack-3d-card {
opacity: 1;
transform: translateY(calc((var(--i) - 1) * 3.6em));
}
@media (prefers-reduced-motion: reduce) {
.hfx-notification-stack-3d, .hfx-notification-stack-3d *, .hfx-notification-stack-3d::before, .hfx-notification-stack-3d::after, .hfx-notification-stack-3d *::before, .hfx-notification-stack-3d *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--hfx-color on the element or a parent.
Currently previewing #3b82f6.--hfx-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.