Frosted glass
A translucent pane over slowly moving color.
A frosted macOS style dock over a drifting wallpaper. Icons magnify under the cursor, neighbors grow a little with :has(), a label pops up, and one app bounces like it is launching.
Also known as: macos dock css, dock magnification css, glass dock css.
<div class="gls-macos-glass-dock">
<nav class="gls-macos-glass-dock-dock" aria-label="Dock">
<a class="gls-macos-glass-dock-app" href="#" aria-label="Files">
<svg viewBox="0 0 24 24" fill="#fff" aria-hidden="true"><path d="M3 7.5A2.5 2.5 0 0 1 5.5 5H9l2 2h7.5A2.5 2.5 0 0 1 21 9.5v7a2.5 2.5 0 0 1-2.5 2.5h-13A2.5 2.5 0 0 1 3 16.5z"/></svg>
</a>
<a class="gls-macos-glass-dock-app" href="#" aria-label="Messages">
<svg viewBox="0 0 24 24" fill="#fff" aria-hidden="true"><path d="M12 4c4.7 0 8.5 3 8.5 6.8s-3.8 6.8-8.5 6.8c-1 0-2-.1-2.9-.4L4.5 19l1.3-3.4C4.4 14.4 3.5 12.7 3.5 10.8 3.5 7 7.3 4 12 4z"/></svg>
</a>
<a class="gls-macos-glass-dock-app" href="#" aria-label="Music">
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 17.5V6l10-2v11.5"/><circle cx="6.5" cy="17.5" r="2.5" fill="#fff"/><circle cx="16.5" cy="15.5" r="2.5" fill="#fff"/></svg>
</a>
<a class="gls-macos-glass-dock-app" href="#" aria-label="Photos">
<svg viewBox="0 0 24 24" aria-hidden="true"><g opacity="0.85"><circle cx="12" cy="7.5" r="4.5" fill="#f59e0b"/><circle cx="16.5" cy="12" r="4.5" fill="#22c55e"/><circle cx="12" cy="16.5" r="4.5" fill="#3b82f6"/><circle cx="7.5" cy="12" r="4.5" fill="#ec4899"/></g></svg>
</a>
<a class="gls-macos-glass-dock-app" href="#" aria-label="Calendar">
<svg viewBox="0 0 24 24" aria-hidden="true"><text x="12" y="8.5" text-anchor="middle" font-family="system-ui, sans-serif" font-size="5.5" font-weight="700" fill="#ef4444">SEP</text><text x="12" y="19.5" text-anchor="middle" font-family="system-ui, sans-serif" font-size="11" font-weight="600" fill="#111827">29</text></svg>
</a>
<a class="gls-macos-glass-dock-app" href="#" aria-label="Settings">
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" aria-hidden="true"><circle cx="12" cy="12" r="6.6" stroke-width="3.6" stroke-dasharray="2.6 2.58"/><circle cx="12" cy="12" r="4.2" stroke-width="2.2"/></svg>
</a>
</nav>
</div>.gls-macos-glass-dock {
--c: var(--gls-color, #3b82f6);
font-size: var(--gls-size, 16px);
position: relative;
display: grid;
place-items: center;
width: 22em;
height: 12em;
border-radius: 1.25em;
overflow: hidden;
isolation: isolate;
background: linear-gradient(170deg, color-mix(in srgb, var(--c) 40%, #1e1b4b), color-mix(in srgb, var(--c) 25%, #6d28d9));
color: #fff;
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.3;
}
.gls-macos-glass-dock::before,
.gls-macos-glass-dock::after {
content: "";
position: absolute;
z-index: -1;
border-radius: 50%;
}
.gls-macos-glass-dock::before {
left: -25%;
top: 48%;
width: 95%;
height: 120%;
background: linear-gradient(20deg, var(--c), color-mix(in srgb, var(--c) 40%, #fff) 70%);
box-shadow: 0 0 2em color-mix(in srgb, var(--c) 60%, transparent);
animation: gls-macos-glass-dock-wave 9s ease-in-out infinite alternate;
}
.gls-macos-glass-dock::after {
right: -30%;
top: 38%;
width: 85%;
height: 110%;
background: linear-gradient(200deg, #fde68a, #fb7185 60%);
box-shadow: 0 0 2em rgb(251 113 133 / 0.5);
animation: gls-macos-glass-dock-wave 11s ease-in-out -5s infinite alternate-reverse;
}
.gls-macos-glass-dock-dock {
position: absolute;
left: 50%;
bottom: 0.6em;
display: flex;
align-items: flex-end;
gap: 0.4em;
padding: 0.38em 0.45em 0.5em;
border-radius: 1.05em;
translate: -50% 0;
background: color-mix(in srgb, #fff 18%, transparent);
-webkit-backdrop-filter: blur(0.9em) saturate(1.5);
backdrop-filter: blur(0.9em) saturate(1.5);
border: 1px solid color-mix(in srgb, #fff 45%, transparent);
box-shadow: 0 0.5em 1.5em -0.5em rgb(0 0 0 / 0.35), inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
}
.gls-macos-glass-dock-app {
position: relative;
display: grid;
place-items: center;
width: 2.2em;
height: 2.2em;
border-radius: 0.55em;
background: var(--tile);
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.5), 0 0.15em 0.35em rgb(0 0 0 / 0.3);
transform-origin: 50% 100%;
transition: scale 0.25s ease, translate 0.25s ease, margin 0.25s ease;
}
.gls-macos-glass-dock-app svg { width: 1.35em; height: 1.35em; }
.gls-macos-glass-dock-app:nth-child(1) { --tile: linear-gradient(180deg, color-mix(in srgb, var(--c) 45%, #fff), var(--c)); }
.gls-macos-glass-dock-app:nth-child(2) { --tile: linear-gradient(180deg, #86efac, #16a34a); }
.gls-macos-glass-dock-app:nth-child(3) { --tile: linear-gradient(180deg, #fda4af, #e11d48); }
.gls-macos-glass-dock-app:nth-child(4) {
--tile: #fff;
animation: gls-macos-glass-dock-launch 4s linear infinite;
}
.gls-macos-glass-dock-app:nth-child(5) { --tile: #fff; }
.gls-macos-glass-dock-app:nth-child(6) { --tile: linear-gradient(180deg, #d1d5db, #6b7280); }
/* running indicator */
.gls-macos-glass-dock-app:nth-child(-n + 3)::before,
.gls-macos-glass-dock-app:nth-child(4)::before {
content: "";
position: absolute;
bottom: -0.32em;
width: 0.2em;
height: 0.2em;
border-radius: 50%;
background: rgb(255 255 255 / 0.85);
}
/* app name above the hovered icon */
.gls-macos-glass-dock-app::after {
content: attr(aria-label);
position: absolute;
bottom: calc(100% + 0.5em);
padding: 0.3em 0.6em;
border-radius: 0.5em;
background: rgb(15 23 42 / 0.6);
color: #fff;
font-size: 0.4em;
font-weight: 600;
white-space: nowrap;
opacity: 0;
translate: 0 0.3em;
transition: opacity 0.2s ease, translate 0.2s ease;
pointer-events: none;
}
.gls-macos-glass-dock-app:hover {
scale: 1.45;
translate: 0 -0.3em;
margin-inline: 0.5em;
}
.gls-macos-glass-dock-app:hover::after { opacity: 1; translate: 0 0; }
.gls-macos-glass-dock-app:has(+ .gls-macos-glass-dock-app:hover),
.gls-macos-glass-dock-app:hover + .gls-macos-glass-dock-app {
scale: 1.2;
translate: 0 -0.12em;
margin-inline: 0.22em;
}
.gls-macos-glass-dock-app:focus-visible { outline: 2px solid #fff; outline-offset: 0.15em; }
@keyframes gls-macos-glass-dock-launch {
0%, 36%, 100% { transform: translateY(0); animation-timing-function: ease-out; }
6% { transform: translateY(-0.9em); animation-timing-function: ease-in; }
12% { transform: translateY(0); animation-timing-function: ease-out; }
18% { transform: translateY(-0.9em); animation-timing-function: ease-in; }
24% { transform: translateY(0); animation-timing-function: ease-out; }
30% { transform: translateY(-0.9em); animation-timing-function: ease-in; }
}
@keyframes gls-macos-glass-dock-wave {
to { transform: translate(12%, -8%) rotate(8deg); }
}
@media (prefers-reduced-motion: reduce) {
.gls-macos-glass-dock, .gls-macos-glass-dock *, .gls-macos-glass-dock::before, .gls-macos-glass-dock::after, .gls-macos-glass-dock *::before, .gls-macos-glass-dock *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--gls-color on the element or a parent.
Currently previewing #3b82f6.--gls-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.