ui-001

macOS Dock Navigation

£10 or the whole Vault, £99

A macOS-style dock menu for app shells and dashboards: icons magnify under the cursor, badges pop, and items drag to reorder.

Flip intermediate
9 more details
micro-interactionhover-effectpointer-trackingquickTospring-physicsdragreorderflip-layoutkeyboard-navigation

This element reads better at your own screen size than in the frame below:

Updated

About this component

A floating dock with everything a dock actually owes: icons magnify and lift smoothly with the pointer, an active-item dot springs between icons as the selection changes, badge counts pop when the number changes and animate away at zero, and a separator divides one group of icons from another.

Read the full effect overview

Icons are dragged to reorder, with the neighbours opening a gap through Flip and the order kept for the session; the divider refuses to be stranded at either end, so the two groups survive any rearrangement. Keyboard users get a roving tabindex with Left/Right, Home/End, Enter to activate and Alt+arrow to reorder, and the label appears on focus exactly as it does on hover. On a touch screen the dock rests flat with full-size targets and a tap reveals the label. A small public API and CustomEvents wire it to the rest of an app, and it ships in light and dark themes on one set of CSS custom properties.

macOS Dock Navigation - UI component preview

What's included

11 items
  • Proximity magnification: a smooth cosine bell curve, damped for a product surface and retunable from CSS
  • quickTo setters throughout: no tween allocated per mouse move
  • An active-item indicator that springs between icons on an elastic ease, plus a press bounce on the icon you click
  • Badge counts that pop when the number changes and shrink away at zero, with the count in the accessible name
  • A separator between two groups of icons that refuses to be stranded at either end of the dock
  • Drag to reorder: neighbours open a gap through Flip and the order is kept for the session
  • Roving tabindex with Left/Right, Home/End, Enter to activate and Alt+arrow to reorder
  • The label appears on keyboard focus exactly as it does on hover, and on a tap where there is no hover
  • Rests flat on touch devices; every icon stays a 48px tap target
  • setActive / setBadge / getOrder on window, plus dock:activate, dock:reorder and dock:badge events
  • Light and dark themes, every colour a custom property defined in both

Perfect for

4 use cases
  • App shells and dashboards with a persistent icon dock
  • Canvas and editor tool switchers, with the current item marked
  • Quick-action docks carrying unread counts
  • Floating bottom navigation on hybrid mobile/desktop layouts

How it works

4 sections

Proximity magnification

As the pointer travels along the dock, each icon scales and lifts by how near it is. The icon under the cursor grows most and its neighbours taper off either side, so there is no boundary where the magnification switches on. The curve's three numbers are CSS custom properties read at use time, so the shape can be retuned without touching JavaScript.

The label rides above the icon

Each icon sits in a slot: the button is what scales, the slot is what holds the label and what moves when icons are reordered. The label lifts by exactly the height the magnified icon gains, so it clears the magnified icon without ever scaling with it. The gap under the label is its own padding rather than a top offset, which keeps the pointer inside a hovered region on the way up.

Reordering with Flip

A drag is judged by net displacement, so a small wobble is still a click. Past the threshold the icon follows the pointer, and each time it crosses a neighbour's resting position the other icons animate to their new places with Flip. Neighbours are compared at rest rather than mid-flight, which is what lets a fast drag cross several icons in one sweep. The separator is part of that sequence, so an icon can change group, but a move that would strand the divider at either end is refused.

The active-item dot

The indicator springs between icons with a soft elastic settle. Its position is derived from the same layout reference as the icons themselves, so the dock's own border and padding cannot double up on the offset and a zoomed or scaled capture frame still lands it dead centre.

Plugins Flip
Difficulty Intermediate
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this UI element's demo, from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Paid UI element

Purchase to unlock the code.

Buying macOS Dock Navigation opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

£10 Standard license, unlimited projects

Browse UI elements

Or the whole library: the Vault, £99, one payment.

Documentation

Quick Start

1. Add to your <head>:

Code snippet omitted: it ships with the download.

2. Add before the closing </body> tag:

Code snippet omitted: it ships with the download.

Flip is optional. Without it everything still works; the reorder snaps instead of sliding.

3. Copy the component markup from index.html — the block between <!-- Component starts here --> and <!-- Component ends here -->.

The script finds everything by data attribute, so there are no ids to keep and you can put more than one dock on a page:

Attribute On Purpose
data-dock the root Marks a dock. data-dock-id names its saved order, data-dock-active sets the icon that starts active
data-dock-row the flex row The element icons are laid out and reordered inside. Keep it position: relative
data-dock-slot one per icon The unit that moves when icons are reordered, and the anchor the label hangs off
data-dock-item the <button> The icon itself. Carries data-id, data-label, data-badge and draggable="false"
data-dock-face inside the button What the press bounce animates, so the bounce and the magnification never write the same transform
data-dock-badge inside the face The count bubble
data-dock-tip after the button The label. .dock-tooltip > span is the visible pill
data-dock-sep in the row The divider. Optional: leave it out and the dock is one group
data-dock-indicator in the row The active-item dot. Optional
data-dock-live in the root A role="status" region for reorder announcements. Optional

Demo-only, not part of what you bought: the toolbar strip across the top (its prompt, the "New message" / "Clear badges" / "Reset order" buttons and the Light/Dark toggle), the .stage wrapper that centres the dock on the grey ground, and the Google Font link. Delete all of it; the component needs none of it. thumbnail.html is a build input for the store listing and is not needed either.

What's Included

  • index.html: the demo page and the markup to copy

  • assets/style.css: component styles, every colour a custom property, light and dark themes

  • assets/script.js: readable, commented source with an onReady guard

  • assets/script.min.js: the minified build

  • Proximity magnification on a cosine bell, applied through gsap.quickTo setters so no tween is allocated per mouse move

  • An active-item dot that springs between icons on an elastic ease, and a press bounce on the icon you click

  • Badge counts that pop when the number changes, shrink away at zero, and put the count in the button's accessible name

  • Drag to reorder, with the neighbours opening a gap through Flip and the order kept in sessionStorage for the session

  • A separator that divides one group of icons from another and refuses to be stranded at either end, so both groups survive any rearrangement

  • Roles: the row is a role="toolbar", every icon is a real <button> with an accessible name, the divider is aria-hidden, the active icon carries aria-current="true", and reorders are announced through a polite role="status" region

Keyboard & Accessibility

Key What it does
Tab Enters the dock at one stop (roving tabindex), and leaves it
← / → Moves focus between icons, wrapping at the ends
Home / End First / last icon
Enter / Space Activates the focused icon: the dot springs to it and the icon bounces
Alt + ← / Alt + → Moves the focused icon one slot, through the same Flip animation as a drag
  • The label appears on keyboard focus exactly as it does on hover, and is dismissed when the mouse takes over so two labels are never open at once.
  • Every icon is a real <button> with an accessible name. A badge count is folded into that name ("Messages, 2 unread"), because a visible number that is not in the accessible name fails WCAG 2.5.3.
  • The active icon carries aria-current="true"; the dot and the divider are decorative and aria-hidden.
  • Reorders are announced through the polite role="status" region ("Files moved to position 3").
  • Coarse pointers get no hover-only state: the dock rests flat, every icon stays a 48px target, and a tap reveals the label for a moment as well as activating the icon.
  • Under prefers-reduced-motion: reduce every state is the same, applied with no animation: no magnification easing, no bounce, no elastic on the dot, no Flip on a reorder.
  • With JavaScript off the dock is a plain row of labelled buttons with working :hover labels; the dot is hidden rather than parked in the wrong place.

How It Works

Proximity magnification. On each pointer move the script measures the distance from the pointer to every icon centre along the dock axis, turns it into a scale and a lift through a cosine bell, and writes them with gsap.quickTo setters — the same three setters per icon for the life of the page, so nothing is allocated per frame. The curve is three CSS custom properties (--dock-max-scale, --dock-falloff, --dock-lift) read at use time, so the shape can be retuned without touching JavaScript.

Three transforms, three elements. The button is what magnifies, the .dock-face inside it is what bounces on a click, and the slot around it is what moves during a reorder. Keeping them separate is why a press bounce never fights the magnification and a drag never fights either. Put no CSS transform on any of the three, or GSAP reads it as a pixel offset it never returns from.

The label rides above the icon. The label has its own quickTo setter, lifted by exactly the height a magnified icon gains, so it clears the magnified icon without scaling with it. The gap under it is the label's own padding-bottom, not a top offset, so the pointer stays inside a hovered region on the way up and the label does not flicker shut.

Reordering with Flip. A gesture is judged by net displacement, so a wobble is still a click and a drag never leaves a stale "ignore the next click" flag behind. Past the threshold the slot follows the pointer; each time it crosses a neighbour's resting centre the slot is re-inserted in the DOM and Flip.from slides the others into their new places. Neighbours are measured at their resting positions (their rect minus their in-flight x) rather than mid-tween, which is what lets one fast sweep cross several icons. The order, separator included, is written to sessionStorage under data-dock-id.

The active-item dot. It sits at left: 0 and is moved with a measured x on an elastic ease. The measurement is offsetLeft-based against the same positioned ancestor as the icons, never a client rect: that keeps the dock's own border and padding from doubling up on the offset, and it stays correct when the page is scaled or zoomed.

Public API

The first dock on the page is exposed as window.floatingDock:

Code snippet omitted: it ships with the download.

Every dock also carries its own API as root.dockApi, so a second dock is reachable without the window global.

Events

The root dispatches bubbling CustomEvents:

Event detail
dock:activate { id, label, index, changed }
dock:reorder { id, order, group, index } — group is 'pinned' or 'recent', the side of the separator the icon landed on
dock:badge { id, count, previous }

Code snippet omitted: it ships with the download.

Customisation

  • The magnification curve is --dock-max-scale, --dock-falloff and --dock-lift at the top of assets/style.css.
  • Colours, radii and shadows are the custom properties in each theme block; the accent is --accent, used for the selected icon, the dot and the focus ring and nothing else.
  • Easing and durations are the gsap.to / gsap.timeline calls in assets/script.js.
  • Icon size is .dock-item's width/height; the badge, dot and separator all size off their own properties, so nothing else needs editing.
  • Drop data-dock-sep for a single-group dock. One divider is supported; the reorder logic treats it as a member of the sequence, which is how it keeps its place.

Themes

Ships in two themes, one design at two sets of token values: dark (the default) and light. Pick one with the attribute:

Code snippet omitted: it ships with the download.

In your own page the attribute can sit on the component's root instead of <body>; the script never reads it, so nothing else changes. The demo's toggle and the ?variant=light URL parameter only set that attribute.

Both blocks in assets/style.css define the same list, so re-valuing one block is a rebrand:

Token What it colours
--accent The selected icon, the active-item dot and the focus ring. Its only job
--dock-bg, --dock-border, --dock-shadow The floating panel
--item-bg, --item-bg-hover, --item-bg-active, --item-border, --item-shadow, --item-drag-shadow The icon buttons at rest, on hover, when selected and while dragged
--item-fg, --item-fg-active The icon strokes
--tooltip-bg, --tooltip-fg, --tooltip-border, --tooltip-shadow, --tooltip-radius The label
--badge-bg, --badge-fg, --badge-ring The count bubble; --badge-ring should match --dock-bg
--indicator-bg, --indicator-radius, --indicator-size The active-item dot
--sep-bg, --sep-width The divider
--dock-radius, --item-radius, --dock-blur Shape, shared by both themes

To match your own product, change --accent and the four surface values in each block; nothing needs a selector override. The ground tokens in the same blocks (--ground, --raised, --hover, --ink, --line…) belong to the demo page rather than the component: keep them for the same neutral surround, or drop them if your app already has its own.

Requirements

  • GSAP 3.12+ (core, plus Flip for the reorder animation)
  • No build step, no framework

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.