15 / 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 Cursor-Lens Glass Nav (GSAP-ready)
A bright refraction lens that follows the cursor across the frosted bar — the effect people install GSAP for. The port keeps the lens itself entirely in utilities and leaves exactly two numbers to JavaScript: an eased --nb15-mx and --nb15-my. Swapping the hand-rolled lerp for gsap.quickTo() is then a three-line change that touches no markup.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="nbtw-15 grid place-items-center w-full min-h-svh p-5 font-nb15-sans bg-[conic-gradient(from_90deg_at_50%_50%,#ff8a00,#e52e71,#7f00ff,#ff8a00)] bg-[length:200%_200%] animate-nb15-bg motion-reduce:animate-none" aria-label="Cursor lens interactive glass nav demo">
<nav class="relative overflow-hidden flex flex-wrap items-center gap-x-5.5 gap-y-3 px-5 py-3.5 rounded-[20px] isolate text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/[0.34] shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_22px_46px_-24px_rgba(0,0,0,.6)] backdrop-blur-[20px] backdrop-saturate-[1.8]" id="nbtw-15-bar" aria-label="Primary">
<span class="absolute inset-0 z-0 rounded-[inherit] pointer-events-none bg-[radial-gradient(140px_140px_at_var(--nb15-mx,50%)_var(--nb15-my,50%),rgba(255,255,255,.55),rgba(255,255,255,.12)_40%,transparent_65%)] [opacity:var(--nb15-on,0)] transition-opacity duration-300 motion-reduce:transition-none" aria-hidden="true"></span>
<span class="relative z-[1] text-base font-extrabold">◇ Refract</span>
<div class="relative z-[1] flex flex-wrap gap-1.5">
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:opacity-100 aria-[current=page]:bg-white/[0.18]" href="#nbtw-15" aria-current="page">Work</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Studio</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Lab</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Contact</a>
</div>
</nav>
</section><section class="nbtw-15 grid place-items-center w-full min-h-svh p-5 font-nb15-sans bg-[conic-gradient(from_90deg_at_50%_50%,#ff8a00,#e52e71,#7f00ff,#ff8a00)] bg-[length:200%_200%] animate-nb15-bg motion-reduce:animate-none" aria-label="Cursor lens interactive glass nav demo">
<nav class="relative overflow-hidden flex flex-wrap items-center gap-x-5.5 gap-y-3 px-5 py-3.5 rounded-[20px] isolate text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/[0.34] shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_22px_46px_-24px_rgba(0,0,0,.6)] backdrop-blur-[20px] backdrop-saturate-[1.8]" id="nbtw-15-bar" aria-label="Primary">
<span class="absolute inset-0 z-0 rounded-[inherit] pointer-events-none bg-[radial-gradient(140px_140px_at_var(--nb15-mx,50%)_var(--nb15-my,50%),rgba(255,255,255,.55),rgba(255,255,255,.12)_40%,transparent_65%)] [opacity:var(--nb15-on,0)] transition-opacity duration-300 motion-reduce:transition-none" aria-hidden="true"></span>
<span class="relative z-[1] text-base font-extrabold">◇ Refract</span>
<div class="relative z-[1] flex flex-wrap gap-1.5">
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:opacity-100 aria-[current=page]:bg-white/[0.18]" href="#nbtw-15" aria-current="page">Work</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Studio</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Lab</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Contact</a>
</div>
</nav>
</section>@import "tailwindcss";
@theme {
--font-nb15-sans: system-ui, 'Segoe UI', sans-serif;
--animate-nb15-bg: nb15-bg 22s ease-in-out infinite;
}
@keyframes nb15-bg {
50% {
background-position: 100% 100%;
}
}@import "tailwindcss";
@theme {
--font-nb15-sans: system-ui, 'Segoe UI', sans-serif;
--animate-nb15-bg: nb15-bg 22s ease-in-out infinite;
}
@keyframes nb15-bg {
50% {
background-position: 100% 100%;
}
}(() => {
const bar = document.querySelector('#nbtw-15-bar');
if (!bar) return;
let tx = 50, ty = 50, cx = 50, cy = 50, raf = null;
// GSAP upgrade: const qx = gsap.quickTo(bar, '--nb15-mx', { duration: .4, unit: '%' });
const loop = () => {
cx += (tx - cx) * 0.18;
cy += (ty - cy) * 0.18;
bar.style.setProperty('--nb15-mx', cx.toFixed(1) + '%');
bar.style.setProperty('--nb15-my', cy.toFixed(1) + '%');
raf = (Math.abs(tx - cx) > 0.1 || Math.abs(ty - cy) > 0.1) ? requestAnimationFrame(loop) : null;
};
bar.addEventListener('pointermove', (e) => {
const r = bar.getBoundingClientRect();
tx = ((e.clientX - r.left) / r.width) * 100;
ty = ((e.clientY - r.top) / r.height) * 100;
bar.style.setProperty('--nb15-on', '1');
if (!raf) raf = requestAnimationFrame(loop);
});
bar.addEventListener('pointerleave', () => bar.style.setProperty('--nb15-on', '0'));
})();(() => {
const bar = document.querySelector('#nbtw-15-bar');
if (!bar) return;
let tx = 50, ty = 50, cx = 50, cy = 50, raf = null;
// GSAP upgrade: const qx = gsap.quickTo(bar, '--nb15-mx', { duration: .4, unit: '%' });
const loop = () => {
cx += (tx - cx) * 0.18;
cy += (ty - cy) * 0.18;
bar.style.setProperty('--nb15-mx', cx.toFixed(1) + '%');
bar.style.setProperty('--nb15-my', cy.toFixed(1) + '%');
raf = (Math.abs(tx - cx) > 0.1 || Math.abs(ty - cy) > 0.1) ? requestAnimationFrame(loop) : null;
};
bar.addEventListener('pointermove', (e) => {
const r = bar.getBoundingClientRect();
tx = ((e.clientX - r.left) / r.width) * 100;
ty = ((e.clientY - r.top) / r.height) * 100;
bar.style.setProperty('--nb15-on', '1');
if (!raf) raf = requestAnimationFrame(loop);
});
bar.addEventListener('pointerleave', () => bar.style.setProperty('--nb15-on', '0'));
})();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-nb15-sans: system-ui, 'Segoe UI', sans-serif;
--animate-nb15-bg: nb15-bg 22s ease-in-out infinite;
}
@keyframes nb15-bg {
50% {
background-position: 100% 100%;
}
}@import "tailwindcss";
@theme {
--font-nb15-sans: system-ui, 'Segoe UI', sans-serif;
--animate-nb15-bg: nb15-bg 22s ease-in-out infinite;
}
@keyframes nb15-bg {
50% {
background-position: 100% 100%;
}
}What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The lens is entirely utilities except for two live coordinates, which is the cleanest possible split: appearance in the markup, motion in the handler.
- Both the position and the presence are custom properties, so one handler owns the whole effect and there is no class to toggle.
- Easing the write is what the effect is actually made of. A raw pointer write snaps; a single-pole lerp gives it the weight people install a tween library for.
- The rAF loop exits once the lens arrives, so an idle bar costs nothing — the difference between shipping this and regretting it.
[opacity:var(--nb15-on,0)]carries its own fallback, otherwise the lens is fully visible before the first pointer event.- The GSAP path is a comment in the script for a reason:
gsap.quickTo()writes the same two properties, so upgrading changes no markup at all.
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 Cursor-Lens Glass Nav (GSAP-ready)
Source: https://codefronts.com/navigation/tailwind-liquid-glass-navbars/tailwind-cursor-lens-glass-nav-gsap-ready/
A bright refraction lens that follows the cursor across the frosted bar — the effect people install GSAP for. The port keeps the lens itself entirely in utilities and leaves exactly two numbers to JavaScript: an eased --nb15-mx and --nb15-my. Swapping the hand-rolled lerp for gsap.quickTo() is then a three-line change that touches no markup.
## HTML
```html
<section class="nbtw-15 grid place-items-center w-full min-h-svh p-5 font-nb15-sans bg-[conic-gradient(from_90deg_at_50%_50%,#ff8a00,#e52e71,#7f00ff,#ff8a00)] bg-[length:200%_200%] animate-nb15-bg motion-reduce:animate-none" aria-label="Cursor lens interactive glass nav demo">
<nav class="relative overflow-hidden flex flex-wrap items-center gap-x-5.5 gap-y-3 px-5 py-3.5 rounded-[20px] isolate text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/[0.34] shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_22px_46px_-24px_rgba(0,0,0,.6)] backdrop-blur-[20px] backdrop-saturate-[1.8]" id="nbtw-15-bar" aria-label="Primary">
<span class="absolute inset-0 z-0 rounded-[inherit] pointer-events-none bg-[radial-gradient(140px_140px_at_var(--nb15-mx,50%)_var(--nb15-my,50%),rgba(255,255,255,.55),rgba(255,255,255,.12)_40%,transparent_65%)] [opacity:var(--nb15-on,0)] transition-opacity duration-300 motion-reduce:transition-none" aria-hidden="true"></span>
<span class="relative z-[1] text-base font-extrabold">◇ Refract</span>
<div class="relative z-[1] flex flex-wrap gap-1.5">
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:opacity-100 aria-[current=page]:bg-white/[0.18]" href="#nbtw-15" aria-current="page">Work</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Studio</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Lab</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Contact</a>
</div>
</nav>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--font-nb15-sans: system-ui, 'Segoe UI', sans-serif;
--animate-nb15-bg: nb15-bg 22s ease-in-out infinite;
}
@keyframes nb15-bg {
50% {
background-position: 100% 100%;
}
}
```
## JavaScript
```js
(() => {
const bar = document.querySelector('#nbtw-15-bar');
if (!bar) return;
let tx = 50, ty = 50, cx = 50, cy = 50, raf = null;
// GSAP upgrade: const qx = gsap.quickTo(bar, '--nb15-mx', { duration: .4, unit: '%' });
const loop = () => {
cx += (tx - cx) * 0.18;
cy += (ty - cy) * 0.18;
bar.style.setProperty('--nb15-mx', cx.toFixed(1) + '%');
bar.style.setProperty('--nb15-my', cy.toFixed(1) + '%');
raf = (Math.abs(tx - cx) > 0.1 || Math.abs(ty - cy) > 0.1) ? requestAnimationFrame(loop) : null;
};
bar.addEventListener('pointermove', (e) => {
const r = bar.getBoundingClientRect();
tx = ((e.clientX - r.left) / r.width) * 100;
ty = ((e.clientY - r.top) / r.height) * 100;
bar.style.setProperty('--nb15-on', '1');
if (!raf) raf = requestAnimationFrame(loop);
});
bar.addEventListener('pointerleave', () => bar.style.setProperty('--nb15-on', '0'));
})();
```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 Cursor-Lens Glass Nav (GSAP-ready)
Source: https://codefronts.com/navigation/tailwind-liquid-glass-navbars/tailwind-cursor-lens-glass-nav-gsap-ready/
A bright refraction lens that follows the cursor across the frosted bar — the effect people install GSAP for. The port keeps the lens itself entirely in utilities and leaves exactly two numbers to JavaScript: an eased --nb15-mx and --nb15-my. Swapping the hand-rolled lerp for gsap.quickTo() is then a three-line change that touches no markup.
## HTML
```html
<section class="nbtw-15 grid place-items-center w-full min-h-svh p-5 font-nb15-sans bg-[conic-gradient(from_90deg_at_50%_50%,#ff8a00,#e52e71,#7f00ff,#ff8a00)] bg-[length:200%_200%] animate-nb15-bg motion-reduce:animate-none" aria-label="Cursor lens interactive glass nav demo">
<nav class="relative overflow-hidden flex flex-wrap items-center gap-x-5.5 gap-y-3 px-5 py-3.5 rounded-[20px] isolate text-white bg-[color-mix(in_oklab,white_12%,transparent)] border border-white/[0.34] shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_22px_46px_-24px_rgba(0,0,0,.6)] backdrop-blur-[20px] backdrop-saturate-[1.8]" id="nbtw-15-bar" aria-label="Primary">
<span class="absolute inset-0 z-0 rounded-[inherit] pointer-events-none bg-[radial-gradient(140px_140px_at_var(--nb15-mx,50%)_var(--nb15-my,50%),rgba(255,255,255,.55),rgba(255,255,255,.12)_40%,transparent_65%)] [opacity:var(--nb15-on,0)] transition-opacity duration-300 motion-reduce:transition-none" aria-hidden="true"></span>
<span class="relative z-[1] text-base font-extrabold">◇ Refract</span>
<div class="relative z-[1] flex flex-wrap gap-1.5">
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:opacity-100 aria-[current=page]:bg-white/[0.18]" href="#nbtw-15" aria-current="page">Work</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Studio</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Lab</a>
<a class="flex items-center min-h-10 px-4 rounded-xl no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.18] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-15">Contact</a>
</div>
</nav>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--font-nb15-sans: system-ui, 'Segoe UI', sans-serif;
--animate-nb15-bg: nb15-bg 22s ease-in-out infinite;
}
@keyframes nb15-bg {
50% {
background-position: 100% 100%;
}
}
```
## JavaScript
```js
(() => {
const bar = document.querySelector('#nbtw-15-bar');
if (!bar) return;
let tx = 50, ty = 50, cx = 50, cy = 50, raf = null;
// GSAP upgrade: const qx = gsap.quickTo(bar, '--nb15-mx', { duration: .4, unit: '%' });
const loop = () => {
cx += (tx - cx) * 0.18;
cy += (ty - cy) * 0.18;
bar.style.setProperty('--nb15-mx', cx.toFixed(1) + '%');
bar.style.setProperty('--nb15-my', cy.toFixed(1) + '%');
raf = (Math.abs(tx - cx) > 0.1 || Math.abs(ty - cy) > 0.1) ? requestAnimationFrame(loop) : null;
};
bar.addEventListener('pointermove', (e) => {
const r = bar.getBoundingClientRect();
tx = ((e.clientX - r.left) / r.width) * 100;
ty = ((e.clientY - r.top) / r.height) * 100;
bar.style.setProperty('--nb15-on', '1');
if (!raf) raf = requestAnimationFrame(loop);
});
bar.addEventListener('pointerleave', () => bar.style.setProperty('--nb15-on', '0'));
})();
```How this works
The lens is a utility; its position is data. Everything visual is declared once:
class="absolute inset-0 z-0 rounded-[inherit] pointer-events-none
bg-[radial-gradient(140px_140px_at_var(--nb15-mx,50%)_var(--nb15-my,50%),rgba(255,255,255,.55),rgba(255,255,255,.12)_40%,transparent_65%)]
[opacity:var(--nb15-on,0)] transition-opacity duration-300"Easing the write is the whole feel. Writing the raw pointer position each frame produces a lens that snaps to the cursor and reads as cheap. A single-pole lerp — cx += (tx - cx) * 0.18 — gives it the weight people associate with a tweened library, at a fraction of the cost:
// GSAP: const qx = gsap.quickTo(bar, '--nb15-mx', { duration: 0.4, unit: '%' });
cx += (tx - cx) * 0.18;
bar.style.setProperty('--nb15-mx', cx.toFixed(1) + '%');The loop stops when it arrives. The rAF chain exits once the lens is within a tenth of a percent of the target, so an idle bar costs nothing — the discipline that separates a lens you can ship from one that burns a core.
Fading is --nb15-on, not a class. Pointer enter sets it to 1, leave sets it to 0, and the opacity transition does the rest. Keeping both the position and the presence as properties is what lets one handler own the whole effect.
Make it yours
- Replace the lerp with
gsap.quickTo()on both properties — same variables, same markup, better easing for free. - Resize the lens with the two lengths in the gradient, or soften it by moving the second stop outward.
- Tint the lens by swapping the white stops for a
color-mix()with your accent; keep the outer stop transparent. - Add literal warp by pairing the lens with the SVG displacement filter from demo 03 on a sibling layer.
- Drive it from focus as well as pointer by writing the coordinates of the focused link's centre on
focusin.
Gotchas — read before shipping
- Ease the property writes. An unsmoothed lens snaps to the cursor and immediately reads as cheap — this is the difference the GSAP crowd is paying for.
- Exit the rAF loop when the lens reaches its target. A permanently running loop on a resting navbar is a real battery cost.
- Keep the lens
aria-hiddenandpointer-events-none; a full-bleed decorative layer over a nav is otherwise a click sink. [opacity:var(--nb15-on,0)]needs the fallback in thevar(), or the lens is fully opaque before the first pointer event.- Only write when the pointer has actually moved. Firing on every event regardless is what makes this pattern show up in INP traces.
- Touch devices have no hover. The lens simply never appears there, which is fine — do not put anything essential inside it.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Tailwind v4 sets the floor. The lens is enhancement: with no JS the bar is a static frosted nav, and GSAP is entirely optional — the vanilla lerp shown here is the whole dependency.