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
A five-tab bottom bar with an active state, an unread badge and safe-area padding for notched phones.
<nav class="tabbar" aria-label="Primary">
<ul class="tabbar__list">
<li class="tabbar__item">
<a class="tabbar__link" href="/" aria-current="page">
<svg class="tabbar__icon" 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 9-7.5 9 7.5" />
<path d="M5 9.5V20a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9.5" />
</svg>
<span class="tabbar__label">Home</span>
</a>
</li>
<li class="tabbar__item">
<a class="tabbar__link" href="/search">
<svg class="tabbar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<circle cx="11" cy="11" r="7" />
<path d="m20 20-3.6-3.6" />
</svg>
<span class="tabbar__label">Search</span>
</a>
</li>
<li class="tabbar__item">
<a class="tabbar__link" href="/create">
<svg class="tabbar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<rect x="4" y="4" width="16" height="16" rx="4" />
<path d="M12 9v6M9 12h6" />
</svg>
<span class="tabbar__label">Create</span>
</a>
</li>
<li class="tabbar__item">
<a class="tabbar__link" href="/inbox">
<span class="tabbar__iconwrap">
<svg class="tabbar__icon" 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 13h4" />
<path d="M5.5 5h13l2.5 8v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-5Z" />
</svg>
<!-- The badge is decoration; the count also rides along in the -->
<!-- link's own text so it is announced, not just seen. -->
<span class="tabbar__badge" aria-hidden="true">3</span>
</span>
<span class="tabbar__label">Inbox</span>
<span class="tabbar__sr">3 unread</span>
</a>
</li>
<li class="tabbar__item">
<a class="tabbar__link" href="/profile">
<svg class="tabbar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<circle cx="12" cy="8" r="3.5" />
<path d="M5 20a7 7 0 0 1 14 0" />
</svg>
<span class="tabbar__label">Profile</span>
</a>
</li>
</ul>
</nav>| Prop | Type | Default | Description |
|---|---|---|---|
tabs | readonly TabItem[] | DEFAULT_TABS | The array of items to render. |
currentPath | string | - | The current path. The item matching it is marked as the active page. |
count | number | - | Number shown in the badge; anything above 99 collapses to "99+". |
onSelect | (id: string) => void | - | Fired with the menu item the user chose. |
ariaLabel | string | 'Primary' | The button's accessible name. Required for icon-only buttons. |
className | string | - | Additional classes merged onto the root element. |
Two details separate a tab bar that works on a real phone from one that only works in a screenshot. The first is `env(safe-area-inset-bottom)`: without it the labels sit under the home indicator on any modern iPhone, and you will not notice in a desktop browser. The second is the touch target - `min-h-[3.25rem]` with the label inside keeps every tab past the 44px floor even though the icon itself is 22px. The active tab is marked with `aria-current="page"` and the blue is styled off that attribute, so the colour and the announcement cannot drift apart; drive it from `usePathname()`. The badge is `aria-hidden` and the count is repeated in a clipped span - a bare red circle is decoration, "3 unread" is the information. Anything above 99 collapses to "99+" so a runaway count cannot stretch the tab. The bar is opaque rather than translucent on purpose: it lives under a thumb, over scrolling content, outdoors, and a blurred surface is where label contrast quietly dies.