17 / 17
All 17 in 17 Tailwind CSS Side Menus
- 01Tailwind Slide-In Side Menu with Animated Hamburger Icon
- 02Tailwind Sidebar Drawer with Blurred Backdrop Overlay
- 03Tailwind Off-Canvas Push Sidebar Menu
- 04Tailwind Collapsible Icon Sidebar That Expands on Hover
- 05Responsive Tailwind Sidebar with Mobile Hamburger Toggle
- 06Tailwind Sticky Sidebar Navigation with Scrolling Content
- 07Tailwind Multi-Level Dropdown Sidebar Menu (Nested Accordion)
- 08Tailwind Full-Height Flexbox Sidebar Layout
- 09Tailwind Neumorphism Sidebar Menu (Dark Mode)
- 10Tailwind Glassmorphism Sidebar with Backdrop Blur
- 11Tailwind Cyberpunk Neon Sidebar (Dark Mode)
- 12Brutalist Sidebar Navigation in Tailwind CSS
- 13Pure CSS Hamburger Menu in Tailwind (Checkbox Hack, No JS)
- 14Tailwind Sidebar Drawer That Opens on Hover
- 15Tailwind Collapsible Sidebar with Toggle Button (No JS)
- 16Tailwind Accordion Sidebar Navigation for Documentation
- 17Tailwind macOS-Style Glass Dock Sidebar with Sliding Highlight
Tailwind macOS-Style Glass Dock Sidebar with Sliding Highlight
A floating vertical dock in dark frosted glass: icons at rest, labels springing open on hover or focus, and a glowing highlight pill that slides to whichever item you point at. The port makes the dock actually work — clicking an item moves aria-current, updates the page title and parks the highlight there; arrow keys move focus up and down the dock.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tsm-17 flex w-full min-h-svh items-center gap-8 bg-[radial-gradient(120%_120%_at_10%_10%,var(--color-tsm17-bg1),var(--color-tsm17-bg2)_70%)] p-6 font-tsm17-sans text-white" aria-label="macOS-style glass dock sidebar demo">
<nav data-tsm17-dock aria-label="Primary" class="group/dock relative isolate flex w-[66px] shrink-0 flex-col gap-1.5 overflow-hidden rounded-[22px] border border-white/16 bg-white/8 p-3 shadow-[inset_0_1px_0_oklch(1_0_0/0.28),0_30px_60px_-28px_oklch(0_0_0/0.8)] backdrop-blur-[22px] backdrop-saturate-160 transition-[width] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] hover:w-[210px] focus-within:w-[210px] motion-reduce:transition-none">
<span data-hl aria-hidden="true" class="pointer-events-none absolute top-0 right-3 left-3 h-11 translate-y-[var(--tsm17-y,12px)] rounded-xl bg-tsm17-acc/70 shadow-[0_6px_18px_-6px_var(--color-tsm17-acc)] transition-[translate] duration-400 ease-[cubic-bezier(0.34,1.35,0.5,1)] motion-reduce:transition-none"></span>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white" aria-current="page"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V20h14V9.5"/><path d="M10 20v-6h4v6"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Dashboard</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3v18h18"/><path d="m7 15 4-4 3 3 5-6"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Analytics</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Projects</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a8 8 0 0 1-11.6 7.1L4 20l1-4.6A8 8 0 1 1 21 12z"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Messages</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h9"/><path d="M19 6h1"/><circle cx="16" cy="6" r="2.5"/><path d="M4 12h2"/><path d="M12 12h8"/><circle cx="9" cy="12" r="2.5"/><path d="M4 18h11"/><circle cx="18" cy="18" r="2.5"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Settings</span></a>
</nav>
<div class="min-w-0">
<p class="text-xs font-semibold tracking-[0.14em] text-white/60 uppercase">Current page</p>
<h1 data-tsm17-title class="mt-1 text-[clamp(28px,5vw,44px)] font-bold tracking-tight">Dashboard</h1>
<p class="mt-2 max-w-xs text-sm leading-relaxed text-white/70 text-pretty">Hover or Tab into the dock. Click an item — or use the arrow keys — and the highlight slides with you.</p>
</div>
</section><section class="tsm-17 flex w-full min-h-svh items-center gap-8 bg-[radial-gradient(120%_120%_at_10%_10%,var(--color-tsm17-bg1),var(--color-tsm17-bg2)_70%)] p-6 font-tsm17-sans text-white" aria-label="macOS-style glass dock sidebar demo">
<nav data-tsm17-dock aria-label="Primary" class="group/dock relative isolate flex w-[66px] shrink-0 flex-col gap-1.5 overflow-hidden rounded-[22px] border border-white/16 bg-white/8 p-3 shadow-[inset_0_1px_0_oklch(1_0_0/0.28),0_30px_60px_-28px_oklch(0_0_0/0.8)] backdrop-blur-[22px] backdrop-saturate-160 transition-[width] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] hover:w-[210px] focus-within:w-[210px] motion-reduce:transition-none">
<span data-hl aria-hidden="true" class="pointer-events-none absolute top-0 right-3 left-3 h-11 translate-y-[var(--tsm17-y,12px)] rounded-xl bg-tsm17-acc/70 shadow-[0_6px_18px_-6px_var(--color-tsm17-acc)] transition-[translate] duration-400 ease-[cubic-bezier(0.34,1.35,0.5,1)] motion-reduce:transition-none"></span>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white" aria-current="page"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V20h14V9.5"/><path d="M10 20v-6h4v6"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Dashboard</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3v18h18"/><path d="m7 15 4-4 3 3 5-6"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Analytics</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Projects</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a8 8 0 0 1-11.6 7.1L4 20l1-4.6A8 8 0 1 1 21 12z"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Messages</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h9"/><path d="M19 6h1"/><circle cx="16" cy="6" r="2.5"/><path d="M4 12h2"/><path d="M12 12h8"/><circle cx="9" cy="12" r="2.5"/><path d="M4 18h11"/><circle cx="18" cy="18" r="2.5"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Settings</span></a>
</nav>
<div class="min-w-0">
<p class="text-xs font-semibold tracking-[0.14em] text-white/60 uppercase">Current page</p>
<h1 data-tsm17-title class="mt-1 text-[clamp(28px,5vw,44px)] font-bold tracking-tight">Dashboard</h1>
<p class="mt-2 max-w-xs text-sm leading-relaxed text-white/70 text-pretty">Hover or Tab into the dock. Click an item — or use the arrow keys — and the highlight slides with you.</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-tsm17-bg1: #1e1b3a;
--color-tsm17-bg1: oklch(0.243 0.058 285.7);
--color-tsm17-bg2: #0b0a17;
--color-tsm17-bg2: oklch(0.154 0.027 286.2);
--color-tsm17-acc: #7b6cf6;
--color-tsm17-acc: oklch(0.617 0.199 283.8);
--font-tsm17-sans: 'Geist', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tsm-17 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tsm17-bg1: #1e1b3a;
--color-tsm17-bg1: oklch(0.243 0.058 285.7);
--color-tsm17-bg2: #0b0a17;
--color-tsm17-bg2: oklch(0.154 0.027 286.2);
--color-tsm17-acc: #7b6cf6;
--color-tsm17-acc: oklch(0.617 0.199 283.8);
--font-tsm17-sans: 'Geist', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tsm-17 {
width: 100%;
min-height: 100svh;
}(() => {
document.querySelectorAll('[data-tsm17-dock]').forEach((dock) => {
if (dock.dataset.wired) return;
dock.dataset.wired = '1';
const hl = dock.querySelector('[data-hl]');
const items = [...dock.querySelectorAll('a')];
const title = dock.closest('.tsm-17')?.querySelector('[data-tsm17-title]');
const current = () => dock.querySelector('[aria-current="page"]') || items[0];
const move = (el) => hl.style.setProperty('--tsm17-y', el.offsetTop + 'px');
// The browser build compiles after this runs, so re-measure once styles, fonts and size settle.
const home = () => { if (!dock.matches(':hover, :focus-within')) move(current()); };
home();
window.addEventListener('load', home);
if (window.ResizeObserver) new ResizeObserver(home).observe(dock);
items.forEach((it, i) => {
it.addEventListener('pointerenter', () => move(it));
it.addEventListener('focus', () => move(it));
it.addEventListener('click', (e) => {
e.preventDefault();
current().removeAttribute('aria-current');
it.setAttribute('aria-current', 'page');
if (title) title.textContent = it.textContent.trim();
move(it);
});
it.addEventListener('keydown', (e) => {
const d = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : 0;
if (!d) return;
e.preventDefault();
items[(i + d + items.length) % items.length].focus();
});
});
dock.addEventListener('pointerleave', () => move(current()));
dock.addEventListener('focusout', (e) => { if (!dock.contains(e.relatedTarget)) move(current()); });
});
})();(() => {
document.querySelectorAll('[data-tsm17-dock]').forEach((dock) => {
if (dock.dataset.wired) return;
dock.dataset.wired = '1';
const hl = dock.querySelector('[data-hl]');
const items = [...dock.querySelectorAll('a')];
const title = dock.closest('.tsm-17')?.querySelector('[data-tsm17-title]');
const current = () => dock.querySelector('[aria-current="page"]') || items[0];
const move = (el) => hl.style.setProperty('--tsm17-y', el.offsetTop + 'px');
// The browser build compiles after this runs, so re-measure once styles, fonts and size settle.
const home = () => { if (!dock.matches(':hover, :focus-within')) move(current()); };
home();
window.addEventListener('load', home);
if (window.ResizeObserver) new ResizeObserver(home).observe(dock);
items.forEach((it, i) => {
it.addEventListener('pointerenter', () => move(it));
it.addEventListener('focus', () => move(it));
it.addEventListener('click', (e) => {
e.preventDefault();
current().removeAttribute('aria-current');
it.setAttribute('aria-current', 'page');
if (title) title.textContent = it.textContent.trim();
move(it);
});
it.addEventListener('keydown', (e) => {
const d = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : 0;
if (!d) return;
e.preventDefault();
items[(i + d + items.length) % items.length].focus();
});
});
dock.addEventListener('pointerleave', () => move(current()));
dock.addEventListener('focusout', (e) => { if (!dock.contains(e.relatedTarget)) move(current()); });
});
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tsm17-bg1: #1e1b3a;
--color-tsm17-bg1: oklch(0.243 0.058 285.7);
--color-tsm17-bg2: #0b0a17;
--color-tsm17-bg2: oklch(0.154 0.027 286.2);
--color-tsm17-acc: #7b6cf6;
--color-tsm17-acc: oklch(0.617 0.199 283.8);
--font-tsm17-sans: 'Geist', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tsm-17 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tsm17-bg1: #1e1b3a;
--color-tsm17-bg1: oklch(0.243 0.058 285.7);
--color-tsm17-bg2: #0b0a17;
--color-tsm17-bg2: oklch(0.154 0.027 286.2);
--color-tsm17-acc: #7b6cf6;
--color-tsm17-acc: oklch(0.617 0.199 283.8);
--font-tsm17-sans: 'Geist', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tsm-17 {
width: 100%;
min-height: 100svh;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The
.hl { transform: translateY(var(--y)) }rule becametranslate-y-[var(--tsm17-y,12px)]; the script still writes only that one property, now namespaced so several docks can share a page. - Label reveal (
.dock:hover .lb) becamegroup-hover/dock:max-w-32plus a matchinggroup-focus-within/dock:pair. - UI upgrade from the CSS original: clicking an item now really selects it —
aria-currentmoves, the page title updates and the highlight parks there. The original highlight always snapped back to Dashboard. - UI upgrade: ArrowUp/ArrowDown move focus through the dock, the pill returns home when focus leaves, unicode glyphs became SVG icons, and Segoe UI became Geist for consistent cross-platform rendering.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind macOS-Style Glass Dock Sidebar with Sliding Highlight
Source: https://codefronts.com/navigation/tailwind-css-side-menus/tailwind-macos-glass-dock-sidebar-sliding-highlight/
A floating vertical dock in dark frosted glass: icons at rest, labels springing open on hover or focus, and a glowing highlight pill that slides to whichever item you point at. The port makes the dock actually work — clicking an item moves aria-current, updates the page title and parks the highlight there; arrow keys move focus up and down the dock.
## HTML
```html
<section class="tsm-17 flex w-full min-h-svh items-center gap-8 bg-[radial-gradient(120%_120%_at_10%_10%,var(--color-tsm17-bg1),var(--color-tsm17-bg2)_70%)] p-6 font-tsm17-sans text-white" aria-label="macOS-style glass dock sidebar demo">
<nav data-tsm17-dock aria-label="Primary" class="group/dock relative isolate flex w-[66px] shrink-0 flex-col gap-1.5 overflow-hidden rounded-[22px] border border-white/16 bg-white/8 p-3 shadow-[inset_0_1px_0_oklch(1_0_0/0.28),0_30px_60px_-28px_oklch(0_0_0/0.8)] backdrop-blur-[22px] backdrop-saturate-160 transition-[width] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] hover:w-[210px] focus-within:w-[210px] motion-reduce:transition-none">
<span data-hl aria-hidden="true" class="pointer-events-none absolute top-0 right-3 left-3 h-11 translate-y-[var(--tsm17-y,12px)] rounded-xl bg-tsm17-acc/70 shadow-[0_6px_18px_-6px_var(--color-tsm17-acc)] transition-[translate] duration-400 ease-[cubic-bezier(0.34,1.35,0.5,1)] motion-reduce:transition-none"></span>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white" aria-current="page"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V20h14V9.5"/><path d="M10 20v-6h4v6"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Dashboard</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3v18h18"/><path d="m7 15 4-4 3 3 5-6"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Analytics</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Projects</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a8 8 0 0 1-11.6 7.1L4 20l1-4.6A8 8 0 1 1 21 12z"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Messages</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h9"/><path d="M19 6h1"/><circle cx="16" cy="6" r="2.5"/><path d="M4 12h2"/><path d="M12 12h8"/><circle cx="9" cy="12" r="2.5"/><path d="M4 18h11"/><circle cx="18" cy="18" r="2.5"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Settings</span></a>
</nav>
<div class="min-w-0">
<p class="text-xs font-semibold tracking-[0.14em] text-white/60 uppercase">Current page</p>
<h1 data-tsm17-title class="mt-1 text-[clamp(28px,5vw,44px)] font-bold tracking-tight">Dashboard</h1>
<p class="mt-2 max-w-xs text-sm leading-relaxed text-white/70 text-pretty">Hover or Tab into the dock. Click an item — or use the arrow keys — and the highlight slides with you.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsm17-bg1: #1e1b3a;
--color-tsm17-bg1: oklch(0.243 0.058 285.7);
--color-tsm17-bg2: #0b0a17;
--color-tsm17-bg2: oklch(0.154 0.027 286.2);
--color-tsm17-acc: #7b6cf6;
--color-tsm17-acc: oklch(0.617 0.199 283.8);
--font-tsm17-sans: 'Geist', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tsm-17 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
document.querySelectorAll('[data-tsm17-dock]').forEach((dock) => {
if (dock.dataset.wired) return;
dock.dataset.wired = '1';
const hl = dock.querySelector('[data-hl]');
const items = [...dock.querySelectorAll('a')];
const title = dock.closest('.tsm-17')?.querySelector('[data-tsm17-title]');
const current = () => dock.querySelector('[aria-current="page"]') || items[0];
const move = (el) => hl.style.setProperty('--tsm17-y', el.offsetTop + 'px');
// The browser build compiles after this runs, so re-measure once styles, fonts and size settle.
const home = () => { if (!dock.matches(':hover, :focus-within')) move(current()); };
home();
window.addEventListener('load', home);
if (window.ResizeObserver) new ResizeObserver(home).observe(dock);
items.forEach((it, i) => {
it.addEventListener('pointerenter', () => move(it));
it.addEventListener('focus', () => move(it));
it.addEventListener('click', (e) => {
e.preventDefault();
current().removeAttribute('aria-current');
it.setAttribute('aria-current', 'page');
if (title) title.textContent = it.textContent.trim();
move(it);
});
it.addEventListener('keydown', (e) => {
const d = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : 0;
if (!d) return;
e.preventDefault();
items[(i + d + items.length) % items.length].focus();
});
});
dock.addEventListener('pointerleave', () => move(current()));
dock.addEventListener('focusout', (e) => { if (!dock.contains(e.relatedTarget)) move(current()); });
});
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind macOS-Style Glass Dock Sidebar with Sliding Highlight
Source: https://codefronts.com/navigation/tailwind-css-side-menus/tailwind-macos-glass-dock-sidebar-sliding-highlight/
A floating vertical dock in dark frosted glass: icons at rest, labels springing open on hover or focus, and a glowing highlight pill that slides to whichever item you point at. The port makes the dock actually work — clicking an item moves aria-current, updates the page title and parks the highlight there; arrow keys move focus up and down the dock.
## HTML
```html
<section class="tsm-17 flex w-full min-h-svh items-center gap-8 bg-[radial-gradient(120%_120%_at_10%_10%,var(--color-tsm17-bg1),var(--color-tsm17-bg2)_70%)] p-6 font-tsm17-sans text-white" aria-label="macOS-style glass dock sidebar demo">
<nav data-tsm17-dock aria-label="Primary" class="group/dock relative isolate flex w-[66px] shrink-0 flex-col gap-1.5 overflow-hidden rounded-[22px] border border-white/16 bg-white/8 p-3 shadow-[inset_0_1px_0_oklch(1_0_0/0.28),0_30px_60px_-28px_oklch(0_0_0/0.8)] backdrop-blur-[22px] backdrop-saturate-160 transition-[width] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] hover:w-[210px] focus-within:w-[210px] motion-reduce:transition-none">
<span data-hl aria-hidden="true" class="pointer-events-none absolute top-0 right-3 left-3 h-11 translate-y-[var(--tsm17-y,12px)] rounded-xl bg-tsm17-acc/70 shadow-[0_6px_18px_-6px_var(--color-tsm17-acc)] transition-[translate] duration-400 ease-[cubic-bezier(0.34,1.35,0.5,1)] motion-reduce:transition-none"></span>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white" aria-current="page"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V20h14V9.5"/><path d="M10 20v-6h4v6"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Dashboard</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3v18h18"/><path d="m7 15 4-4 3 3 5-6"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Analytics</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Projects</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a8 8 0 0 1-11.6 7.1L4 20l1-4.6A8 8 0 1 1 21 12z"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Messages</span></a>
<a href="#" class="group/it relative z-10 flex min-h-11 items-center gap-3.5 rounded-xl px-3 text-[14.5px] font-semibold whitespace-nowrap text-white/80 transition-colors hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white aria-[current=page]:text-white"><svg class="size-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h9"/><path d="M19 6h1"/><circle cx="16" cy="6" r="2.5"/><path d="M4 12h2"/><path d="M12 12h8"/><circle cx="9" cy="12" r="2.5"/><path d="M4 18h11"/><circle cx="18" cy="18" r="2.5"/></svg><span class="max-w-0 overflow-hidden opacity-0 transition-[max-width,opacity] duration-500 ease-[cubic-bezier(0.34,1.35,0.5,1)] group-hover/dock:max-w-32 group-hover/dock:opacity-100 group-focus-within/dock:max-w-32 group-focus-within/dock:opacity-100 motion-reduce:transition-none">Settings</span></a>
</nav>
<div class="min-w-0">
<p class="text-xs font-semibold tracking-[0.14em] text-white/60 uppercase">Current page</p>
<h1 data-tsm17-title class="mt-1 text-[clamp(28px,5vw,44px)] font-bold tracking-tight">Dashboard</h1>
<p class="mt-2 max-w-xs text-sm leading-relaxed text-white/70 text-pretty">Hover or Tab into the dock. Click an item — or use the arrow keys — and the highlight slides with you.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsm17-bg1: #1e1b3a;
--color-tsm17-bg1: oklch(0.243 0.058 285.7);
--color-tsm17-bg2: #0b0a17;
--color-tsm17-bg2: oklch(0.154 0.027 286.2);
--color-tsm17-acc: #7b6cf6;
--color-tsm17-acc: oklch(0.617 0.199 283.8);
--font-tsm17-sans: 'Geist', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
stage is sized before the browser build compiles. */
.tsm-17 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
document.querySelectorAll('[data-tsm17-dock]').forEach((dock) => {
if (dock.dataset.wired) return;
dock.dataset.wired = '1';
const hl = dock.querySelector('[data-hl]');
const items = [...dock.querySelectorAll('a')];
const title = dock.closest('.tsm-17')?.querySelector('[data-tsm17-title]');
const current = () => dock.querySelector('[aria-current="page"]') || items[0];
const move = (el) => hl.style.setProperty('--tsm17-y', el.offsetTop + 'px');
// The browser build compiles after this runs, so re-measure once styles, fonts and size settle.
const home = () => { if (!dock.matches(':hover, :focus-within')) move(current()); };
home();
window.addEventListener('load', home);
if (window.ResizeObserver) new ResizeObserver(home).observe(dock);
items.forEach((it, i) => {
it.addEventListener('pointerenter', () => move(it));
it.addEventListener('focus', () => move(it));
it.addEventListener('click', (e) => {
e.preventDefault();
current().removeAttribute('aria-current');
it.setAttribute('aria-current', 'page');
if (title) title.textContent = it.textContent.trim();
move(it);
});
it.addEventListener('keydown', (e) => {
const d = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : 0;
if (!d) return;
e.preventDefault();
items[(i + d + items.length) % items.length].focus();
});
});
dock.addEventListener('pointerleave', () => move(current()));
dock.addEventListener('focusout', (e) => { if (!dock.contains(e.relatedTarget)) move(current()); });
});
})();
```How this works
CSS owns the look, JS owns one number. The highlight is an absolutely positioned pill with translate-y-(--tsm17-y) and a spring transition-[translate]. The script measures the target item and sets the property — nothing else:
hl.style.setProperty('--tsm17-y', item.offsetTop + 'px');State is an attribute. Clicking an item moves aria-current="page"; styles read it with aria-[current=page]:text-white, and when the pointer leaves the dock the pill returns to the current item.
Expand is pure CSS. Width and label reveal use hover: / focus-within:, so with JS disabled the dock still expands — only the sliding pill is missing.
Make it yours
- Widths: edit
w-[66px]andw-[210px](both the hover and focus-within variants). - Highlight colour: change
--color-tsm17-acc; the pill fill and its glow follow. - Horizontal dock: switch to
flex-row, usetranslate-x-(--tsm17-x)andoffsetLeft. - Less bounce: replace the spring curve with
ease-out.
Gotchas — read before shipping
- Keep labels in the DOM when collapsed —
max-w-0 opacity-0, neverhidden— or icon-only links lose their accessible names. offsetTopis measured from the dock (its offset parent), so the dock must stayrelative.- The demo calls
preventDefault()on clicks for illustration; in production let the links navigate and setaria-currentserver-side.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Hover-expand is CSS; the script only positions the highlight and is progressive enhancement.