App icons in a glass dock that magnify under the pointer while their neighbors grow less and step aside. Pure CSS: sibling selectors for the right side, :has() for the left.
Also known as: dock magnification css, mac dock hover animation, fisheye menu.
<nav class="hfx-dock-magnify" aria-label="Dock"> <a href="#" aria-label="Files" style="--hue: 0deg"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" 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 href="#" aria-label="Mail" style="--hue: 315deg"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2.5"/><path d="m3.5 7 8.5 6 8.5-6"/></svg></a> <a href="#" aria-label="Messages" style="--hue: 270deg"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 11.5a7.5 7.5 0 0 1-10.9 6.7L4 19.5l1.3-4.6A7.5 7.5 0 1 1 20 11.5z"/></svg></a> <a href="#" aria-label="Music" style="--hue: 105deg"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18V5.5l11-2V16"/><circle cx="6.5" cy="18" r="2.5"/><circle cx="17.5" cy="16" r="2.5"/></svg></a> <a href="#" aria-label="Calendar" style="--hue: 150deg"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="5" width="17" height="15.5" rx="2.5"/><path d="M8 3v4M16 3v4M3.5 10h17"/></svg></a> <a href="#" aria-label="Camera" style="--hue: 60deg"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 8.5a2 2 0 0 1 2-2h1.5L9 4.5h6l1.5 2H18a2 2 0 0 1 2 2V17a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z"/><circle cx="12" cy="12.5" r="3.5"/></svg></a> </nav>
.hfx-dock-magnify {
--c: var(--hfx-color, #3b82f6);
font-size: var(--hfx-size, 16px);
position: relative;
isolation: isolate;
display: flex;
align-items: flex-end;
gap: 0.5em;
padding: 0.5em;
}
/* the glass shelf, scaled on X so it widens with the icons */
.hfx-dock-magnify::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border: 1px solid color-mix(in srgb, var(--c) 30%, transparent);
border-radius: 1.1em;
background: color-mix(in srgb, var(--c) 12%, transparent);
-webkit-backdrop-filter: blur(0.75em);
backdrop-filter: blur(0.75em);
transition: transform 0.3s cubic-bezier(0.3, 0.7, 0.4, 1);
}
.hfx-dock-magnify:has(a:is(:hover, :focus-visible))::before { transform: scaleX(1.2); }
.hfx-dock-magnify a {
--x: 0;
--k: 1;
display: grid;
place-items: center;
width: 2.6em;
height: 2.6em;
border-radius: 0.7em;
background: linear-gradient(160deg, color-mix(in srgb, var(--c) 55%, #fff), var(--c) 55%, color-mix(in srgb, var(--c) 75%, #000));
box-shadow: 0 0.3em 0.6em -0.3em rgba(0, 0, 0, 0.45);
color: #fff;
filter: hue-rotate(var(--hue, 0deg));
transform-origin: 50% 100%;
transform: translateX(calc(var(--x) * 2.6em)) translateY(calc((1 - var(--k)) * 0.9em)) scale(var(--k));
transition: transform 0.25s cubic-bezier(0.3, 0.7, 0.4, 1);
}
.hfx-dock-magnify svg { width: 55%; height: 55%; }
/* everything right of the hovered icon shifts right, everything left shifts left */
.hfx-dock-magnify a:is(:hover, :focus-visible) ~ a { --x: 0.72; }
.hfx-dock-magnify a:has(~ a:is(:hover, :focus-visible)) { --x: -0.72; }
/* two away: a little bigger */
.hfx-dock-magnify a:is(:hover, :focus-visible) + a + a { --x: 0.66; --k: 1.12; }
.hfx-dock-magnify a:has(+ a + a:is(:hover, :focus-visible)) { --x: -0.66; --k: 1.12; }
/* direct neighbors */
.hfx-dock-magnify a:is(:hover, :focus-visible) + a { --x: 0.45; --k: 1.3; }
.hfx-dock-magnify a:has(+ a:is(:hover, :focus-visible)) { --x: -0.45; --k: 1.3; }
.hfx-dock-magnify a:is(:hover, :focus-visible) { --x: 0; --k: 1.6; }
@media (prefers-reduced-motion: reduce) {
.hfx-dock-magnify, .hfx-dock-magnify *, .hfx-dock-magnify::before, .hfx-dock-magnify::after, .hfx-dock-magnify *::before, .hfx-dock-magnify *::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.