10 / 24
All 24 in 24 Tailwind Liquid Glass Navbars
- 01Tailwind iOS 26 Floating Tab Bar
- 02Tailwind macOS Tahoe Menu Bar
- 03Tailwind Safari Toolbar with SVG Refraction
- 04Tailwind visionOS Glass Navbar
- 05Tailwind Liquid Glass Bottom Accessory Bar
- 06Tailwind Dynamic Island Morphing Nav
- 07Tailwind Liquid Glass Navbar (Canonical)
- 08Tailwind Responsive Glass Navbar with Burger
- 09Tailwind Next.js Sticky Glass Header
- 10Tailwind Spring Glide Menu (Framer Motion Port)
- 11Tailwind CSS Variables Glass Theme Generator
- 12Tailwind Glass Override for Bootstrap 5 Navbars
- 13Tailwind Scroll-Opacity Sticky Navbar
- 14Tailwind Inner-Glow Glass Nav Links
- 15Tailwind Cursor-Lens Glass Nav (GSAP-ready)
- 16Tailwind Animated Gradient Blobs Glass Navbar
- 17Tailwind Sliding Capsule Tab Bar (group-has)
- 18Tailwind Reflective Glass Sidebar
- 19Tailwind Fallback-First Glass Navbar
- 20Tailwind High-Contrast Accessible Glass Navbar
- 21Tailwind Light / Dark Liquid Glass Navbar
- 22Tailwind Performance-Tuned Glass Navbar
- 23Tailwind Minimalist Dual-Border Glass Header
- 24Tailwind Responsive Glass Sidebar to Bottom Tabs
Tailwind Spring Glide Menu (Framer Motion Port)
The shared-element indicator: one frosted pill glides between links on a spring, the layoutId feel without the library. The port draws a clean line — the pill's look and its transition are utilities, its two measured numbers are inline styles written by the handler, and the notes are explicit that measurement is the one job utilities cannot do.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="nbtw-10 grid place-items-center w-full min-h-svh p-5 font-nb10-sans bg-[conic-gradient(from_30deg_at_40%_40%,#00dbde,#fc00ff,#00dbde)] bg-[length:200%_200%] animate-nb10-bg motion-reduce:animate-none" aria-label="Spring glide menu demo">
<nav class="relative flex flex-wrap gap-1 p-2 rounded-full isolate bg-[color-mix(in_oklab,white_14%,transparent)] border border-white/40 shadow-[inset_0_1px_0_rgba(255,255,255,.55),0_20px_44px_-22px_rgba(0,0,0,.55)] backdrop-blur-[22px] backdrop-saturate-[1.8]" id="nbtw-10-bar" aria-label="Primary">
<span class="absolute inset-y-2 left-2 z-0 w-24 rounded-full bg-white/[0.24] border border-white/50 shadow-[inset_0_1px_0_rgba(255,255,255,.7)] transition-[left,width] duration-500 ease-nb10 motion-reduce:transition-none" id="nbtw-10-pill" aria-hidden="true"></span>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10" aria-current="page">Overview</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Analytics</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Reports</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Settings</a>
</nav>
</section><section class="nbtw-10 grid place-items-center w-full min-h-svh p-5 font-nb10-sans bg-[conic-gradient(from_30deg_at_40%_40%,#00dbde,#fc00ff,#00dbde)] bg-[length:200%_200%] animate-nb10-bg motion-reduce:animate-none" aria-label="Spring glide menu demo">
<nav class="relative flex flex-wrap gap-1 p-2 rounded-full isolate bg-[color-mix(in_oklab,white_14%,transparent)] border border-white/40 shadow-[inset_0_1px_0_rgba(255,255,255,.55),0_20px_44px_-22px_rgba(0,0,0,.55)] backdrop-blur-[22px] backdrop-saturate-[1.8]" id="nbtw-10-bar" aria-label="Primary">
<span class="absolute inset-y-2 left-2 z-0 w-24 rounded-full bg-white/[0.24] border border-white/50 shadow-[inset_0_1px_0_rgba(255,255,255,.7)] transition-[left,width] duration-500 ease-nb10 motion-reduce:transition-none" id="nbtw-10-pill" aria-hidden="true"></span>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10" aria-current="page">Overview</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Analytics</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Reports</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Settings</a>
</nav>
</section>@import "tailwindcss";
@theme {
--font-nb10-sans: system-ui, 'Segoe UI', sans-serif;
--ease-nb10: cubic-bezier(0.34, 1.56, 0.5, 1);
--animate-nb10-bg: nb10-bg 18s ease-in-out infinite;
}
@keyframes nb10-bg {
50% {
background-position: 100% 100%;
}
}@import "tailwindcss";
@theme {
--font-nb10-sans: system-ui, 'Segoe UI', sans-serif;
--ease-nb10: cubic-bezier(0.34, 1.56, 0.5, 1);
--animate-nb10-bg: nb10-bg 18s ease-in-out infinite;
}
@keyframes nb10-bg {
50% {
background-position: 100% 100%;
}
}(() => {
const bar = document.querySelector('#nbtw-10-bar');
const pill = document.querySelector('#nbtw-10-pill');
if (!bar || !pill) return;
const links = [...bar.querySelectorAll('a')];
let active = links.find((l) => l.hasAttribute('aria-current')) || links[0];
const move = (el) => {
const b = bar.getBoundingClientRect(), r = el.getBoundingClientRect();
pill.style.left = (r.left - b.left) + 'px';
pill.style.width = r.width + 'px';
};
links.forEach((l) => {
l.addEventListener('pointerenter', () => move(l));
l.addEventListener('focus', () => move(l));
l.addEventListener('click', (e) => {
e.preventDefault();
links.forEach((x) => x.removeAttribute('aria-current'));
l.setAttribute('aria-current', 'page');
active = l;
});
});
bar.addEventListener('pointerleave', () => move(active));
requestAnimationFrame(() => move(active));
addEventListener('resize', () => move(active));
})();(() => {
const bar = document.querySelector('#nbtw-10-bar');
const pill = document.querySelector('#nbtw-10-pill');
if (!bar || !pill) return;
const links = [...bar.querySelectorAll('a')];
let active = links.find((l) => l.hasAttribute('aria-current')) || links[0];
const move = (el) => {
const b = bar.getBoundingClientRect(), r = el.getBoundingClientRect();
pill.style.left = (r.left - b.left) + 'px';
pill.style.width = r.width + 'px';
};
links.forEach((l) => {
l.addEventListener('pointerenter', () => move(l));
l.addEventListener('focus', () => move(l));
l.addEventListener('click', (e) => {
e.preventDefault();
links.forEach((x) => x.removeAttribute('aria-current'));
l.setAttribute('aria-current', 'page');
active = l;
});
});
bar.addEventListener('pointerleave', () => move(active));
requestAnimationFrame(() => move(active));
addEventListener('resize', () => move(active));
})();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 {
--font-nb10-sans: system-ui, 'Segoe UI', sans-serif;
--ease-nb10: cubic-bezier(0.34, 1.56, 0.5, 1);
--animate-nb10-bg: nb10-bg 18s ease-in-out infinite;
}
@keyframes nb10-bg {
50% {
background-position: 100% 100%;
}
}@import "tailwindcss";
@theme {
--font-nb10-sans: system-ui, 'Segoe UI', sans-serif;
--ease-nb10: cubic-bezier(0.34, 1.56, 0.5, 1);
--animate-nb10-bg: nb10-bg 18s ease-in-out infinite;
}
@keyframes nb10-bg {
50% {
background-position: 100% 100%;
}
}What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The port draws a clean line: the pill's appearance and its transition are utilities, and only the two measured numbers are inline styles. Measurement is the job utilities genuinely cannot do.
- A runtime-computed class name would be invisible to the compiler, which is why
pill.style.leftis correct here rather than a lapse. - The spring is a
--ease-*theme entry, soease-nb10names the curve once instead of four magic numbers repeating across elements. focusis listened to alongsidepointerenter, so tabbing glides the pill too — the half of this pattern most implementations forget.- The indicator is
aria-hiddenand the anchors keeparia-current, so there is exactly one source of truth for which link is active. - Swapping in Framer Motion means deleting the handler and wrapping the indicator in
motion.div layoutId; every utility in the class list stays.
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 Spring Glide Menu (Framer Motion Port)
Source: https://codefronts.com/navigation/tailwind-liquid-glass-navbars/tailwind-spring-glide-menu-framer-motion-port/
The shared-element indicator: one frosted pill glides between links on a spring, the layoutId feel without the library. The port draws a clean line — the pill's look and its transition are utilities, its two measured numbers are inline styles written by the handler, and the notes are explicit that measurement is the one job utilities cannot do.
## HTML
```html
<section class="nbtw-10 grid place-items-center w-full min-h-svh p-5 font-nb10-sans bg-[conic-gradient(from_30deg_at_40%_40%,#00dbde,#fc00ff,#00dbde)] bg-[length:200%_200%] animate-nb10-bg motion-reduce:animate-none" aria-label="Spring glide menu demo">
<nav class="relative flex flex-wrap gap-1 p-2 rounded-full isolate bg-[color-mix(in_oklab,white_14%,transparent)] border border-white/40 shadow-[inset_0_1px_0_rgba(255,255,255,.55),0_20px_44px_-22px_rgba(0,0,0,.55)] backdrop-blur-[22px] backdrop-saturate-[1.8]" id="nbtw-10-bar" aria-label="Primary">
<span class="absolute inset-y-2 left-2 z-0 w-24 rounded-full bg-white/[0.24] border border-white/50 shadow-[inset_0_1px_0_rgba(255,255,255,.7)] transition-[left,width] duration-500 ease-nb10 motion-reduce:transition-none" id="nbtw-10-pill" aria-hidden="true"></span>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10" aria-current="page">Overview</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Analytics</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Reports</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Settings</a>
</nav>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--font-nb10-sans: system-ui, 'Segoe UI', sans-serif;
--ease-nb10: cubic-bezier(0.34, 1.56, 0.5, 1);
--animate-nb10-bg: nb10-bg 18s ease-in-out infinite;
}
@keyframes nb10-bg {
50% {
background-position: 100% 100%;
}
}
```
## JavaScript
```js
(() => {
const bar = document.querySelector('#nbtw-10-bar');
const pill = document.querySelector('#nbtw-10-pill');
if (!bar || !pill) return;
const links = [...bar.querySelectorAll('a')];
let active = links.find((l) => l.hasAttribute('aria-current')) || links[0];
const move = (el) => {
const b = bar.getBoundingClientRect(), r = el.getBoundingClientRect();
pill.style.left = (r.left - b.left) + 'px';
pill.style.width = r.width + 'px';
};
links.forEach((l) => {
l.addEventListener('pointerenter', () => move(l));
l.addEventListener('focus', () => move(l));
l.addEventListener('click', (e) => {
e.preventDefault();
links.forEach((x) => x.removeAttribute('aria-current'));
l.setAttribute('aria-current', 'page');
active = l;
});
});
bar.addEventListener('pointerleave', () => move(active));
requestAnimationFrame(() => move(active));
addEventListener('resize', () => move(active));
})();
```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 Spring Glide Menu (Framer Motion Port)
Source: https://codefronts.com/navigation/tailwind-liquid-glass-navbars/tailwind-spring-glide-menu-framer-motion-port/
The shared-element indicator: one frosted pill glides between links on a spring, the layoutId feel without the library. The port draws a clean line — the pill's look and its transition are utilities, its two measured numbers are inline styles written by the handler, and the notes are explicit that measurement is the one job utilities cannot do.
## HTML
```html
<section class="nbtw-10 grid place-items-center w-full min-h-svh p-5 font-nb10-sans bg-[conic-gradient(from_30deg_at_40%_40%,#00dbde,#fc00ff,#00dbde)] bg-[length:200%_200%] animate-nb10-bg motion-reduce:animate-none" aria-label="Spring glide menu demo">
<nav class="relative flex flex-wrap gap-1 p-2 rounded-full isolate bg-[color-mix(in_oklab,white_14%,transparent)] border border-white/40 shadow-[inset_0_1px_0_rgba(255,255,255,.55),0_20px_44px_-22px_rgba(0,0,0,.55)] backdrop-blur-[22px] backdrop-saturate-[1.8]" id="nbtw-10-bar" aria-label="Primary">
<span class="absolute inset-y-2 left-2 z-0 w-24 rounded-full bg-white/[0.24] border border-white/50 shadow-[inset_0_1px_0_rgba(255,255,255,.7)] transition-[left,width] duration-500 ease-nb10 motion-reduce:transition-none" id="nbtw-10-pill" aria-hidden="true"></span>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10" aria-current="page">Overview</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Analytics</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Reports</a>
<a class="relative z-[1] flex items-center min-h-10 px-4.5 rounded-full no-underline text-white text-[14.5px] font-semibold transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-10">Settings</a>
</nav>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--font-nb10-sans: system-ui, 'Segoe UI', sans-serif;
--ease-nb10: cubic-bezier(0.34, 1.56, 0.5, 1);
--animate-nb10-bg: nb10-bg 18s ease-in-out infinite;
}
@keyframes nb10-bg {
50% {
background-position: 100% 100%;
}
}
```
## JavaScript
```js
(() => {
const bar = document.querySelector('#nbtw-10-bar');
const pill = document.querySelector('#nbtw-10-pill');
if (!bar || !pill) return;
const links = [...bar.querySelectorAll('a')];
let active = links.find((l) => l.hasAttribute('aria-current')) || links[0];
const move = (el) => {
const b = bar.getBoundingClientRect(), r = el.getBoundingClientRect();
pill.style.left = (r.left - b.left) + 'px';
pill.style.width = r.width + 'px';
};
links.forEach((l) => {
l.addEventListener('pointerenter', () => move(l));
l.addEventListener('focus', () => move(l));
l.addEventListener('click', (e) => {
e.preventDefault();
links.forEach((x) => x.removeAttribute('aria-current'));
l.setAttribute('aria-current', 'page');
active = l;
});
});
bar.addEventListener('pointerleave', () => move(active));
requestAnimationFrame(() => move(active));
addEventListener('resize', () => move(active));
})();
```How this works
Utilities style the pill; JavaScript measures it. That split is the whole port. Everything about how the indicator looks and how it moves is in the class list:
class="absolute inset-y-2 left-2 w-24 rounded-full z-0
bg-white/[0.24] border border-white/50
shadow-[inset_0_1px_0_rgba(255,255,255,.7)]
transition-[left,width] duration-500 ease-nb10"and the handler contributes only two numbers that no CSS can know:
const b = bar.getBoundingClientRect(), r = el.getBoundingClientRect();
pill.style.left = (r.left - b.left) + 'px';
pill.style.width = r.width + 'px';The spring is a token. --ease-nb10: cubic-bezier(0.34, 1.56, 0.5, 1) mints ease-nb10, so the curve is named once and every element that should feel the same overshoot asks for it by name rather than repeating four magic numbers.
Hover previews, active commits. Pointer and focus move the pill to the hovered link; leaving the bar returns it to the current one. Clicking rewrites aria-current, which is also what the link styling reads — there is no separate active class to keep in sync.
In Framer Motion this is layoutId. Wrap the indicator in <motion.div layoutId="pill" transition={{type:'spring'}}/>, delete the handler, and keep every utility in the class list exactly as it is.
Make it yours
- Tune the spring by editing
--ease-nb10; more overshoot in the third control point reads bouncier, less reads like glass. - Make it hover-only or active-only by removing one listener set; the markup does not change.
- Switch to Framer Motion with
layoutIdand a spring transition, dropping the two style writes and the resize listener. - Animate
transforminstead ofleftfor a cheaper glide: writetranslate3dand a scaleX from the measured ratio. - Recolour the pill to a brand tint with a
@themecolour if the frosted white reads too neutral in your palette.
Gotchas — read before shipping
- Re-measure on resize and after webfonts load, or the pill drifts out of alignment with no visible cause.
- Keep the indicator
aria-hidden. It is decoration; the anchors carryaria-current, and duplicating state into the pill is how the two get out of sync. leftandwidthare layout properties. They are fine for a small indicator but a transform-based version is the right call inside a scroll container.- Do not try to express the measured values as utilities. A runtime-computed class name is invisible to the compiler — inline styles are correct here.
- Bind
pointerleaveon the bar rather than each link, or the pill jitters as the pointer crosses gaps between links.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Tailwind v4 sets the floor. The glide is enhancement: with no JS the pill sits under the first link and the active link is still marked by its own aria-current styling.