macOS Magnify Dock
A floating icon dock whose items swell under the pointer, the neighbours lifting less than the one you are on.
6 frameworksAdvanced
An icon dock with numeric badges and a status dot, each announced to screen readers.
<!--
Every badge is aria-hidden decoration - the count (or "new activity" for a
bare dot) is repeated in a clipped span so it is announced, not merely seen.
A ring in the surface colour punches the badge off the icon. Above 99 the pill
collapses to "99+" so a runaway count cannot stretch the dock.
-->
<nav
class="fixed bottom-6 left-1/2 inline-block -translate-x-1/2 rounded-2xl border border-black/10 bg-white/85 p-2 shadow-[0_12px_32px_-12px_rgba(0,0,0,0.4)] backdrop-blur-md dark:border-white/10 dark:bg-gray-900/85"
aria-label="Dock"
>
<ul class="flex items-center gap-1">
<li>
<a href="/home" aria-current="page" class="relative flex h-11 w-11 items-center justify-center rounded-xl text-gray-700 hover:bg-black/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-600 aria-[current=page]:text-blue-700 dark:text-gray-200 dark:hover:bg-white/10 dark:focus-visible:ring-blue-400 dark:aria-[current=page]:text-blue-300">
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 10.5 12 3l9 7.5M5 9.5V20a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9.5" /></svg>
<span class="sr-only">Home</span>
</a>
</li>
<li>
<a href="/inbox" class="relative flex h-11 w-11 items-center justify-center rounded-xl text-gray-700 hover:bg-black/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-600 dark:text-gray-200 dark:hover:bg-white/10 dark:focus-visible:ring-blue-400">
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M4 13h4l1.5 3h5L16 13h4M5.5 5h13l2.5 8v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-5Z" /></svg>
<span class="absolute right-1 top-1 min-w-4 rounded-full bg-red-600 px-1 text-center text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white dark:ring-gray-900" aria-hidden="true">8</span>
<span class="sr-only">Inbox, 8 unread</span>
</a>
</li>
<li>
<a href="/chat" class="relative flex h-11 w-11 items-center justify-center rounded-xl text-gray-700 hover:bg-black/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-600 dark:text-gray-200 dark:hover:bg-white/10 dark:focus-visible:ring-blue-400">
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M4 6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H9l-5 4Z" /></svg>
<span class="absolute right-1 top-1 min-w-4 rounded-full bg-red-600 px-1 text-center text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white dark:ring-gray-900" aria-hidden="true">99+</span>
<span class="sr-only">Chat, 128 unread</span>
</a>
</li>
<li>
<a href="/activity" class="relative flex h-11 w-11 items-center justify-center rounded-xl text-gray-700 hover:bg-black/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-600 dark:text-gray-200 dark:hover:bg-white/10 dark:focus-visible:ring-blue-400">
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 12h4l2 6 4-14 2 8h6" /></svg>
<span class="absolute right-1.5 top-1.5 h-2.5 w-2.5 rounded-full bg-emerald-500 ring-2 ring-white dark:ring-gray-900" aria-hidden="true"></span>
<span class="sr-only">Activity, new activity</span>
</a>
</li>
<li>
<a href="/profile" class="relative flex h-11 w-11 items-center justify-center rounded-xl text-gray-700 hover:bg-black/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-600 dark:text-gray-200 dark:hover:bg-white/10 dark:focus-visible:ring-blue-400">
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 4.5a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7ZM5 20a7 7 0 0 1 14 0" /></svg>
<span class="sr-only">Profile</span>
</a>
</li>
</ul>
</nav>| Prop | Type | Default | Description |
|---|---|---|---|
items | readonly BadgeDockItem[] | DEFAULT_ITEMS | The array of items to render. |
onSelect | (id: string) => void | - | Fired with the menu item the user chose. |
ariaLabel | string | 'Dock' | The button's accessible name. Required for icon-only buttons. |
className | string | - | Additional classes merged onto the root element. |
Every badge is `aria-hidden`; the count (or "new activity" for a bare dot) rides along in a clipped span so it is announced, not merely seen. A ring in the surface colour punches the badge off the icon, and any count above 99 collapses to "99+" so a runaway number cannot stretch the dock.