13 / 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 Scroll-Opacity Sticky Navbar
A navbar that starts fully transparent over a hero and dissolves into frosted glass as you scroll — continuously, not at a breakpoint. One registered custom property carries scroll progress from 0 to 1, and four utilities compute the fill, the blur, the border and the text-shadow from it with calc(). There are no transitions in the demo at all: the value is the animation.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="nbtw-13 relative block w-full min-h-svh overflow-hidden font-nb13-sans bg-[#0c1418]" aria-label="Scroll opacity sticky navbar demo">
<div class="absolute inset-0 overflow-y-auto" id="nbtw-13-scroll">
<header class="sticky top-0 z-[6] flex flex-wrap items-center gap-x-4.5 gap-y-3 px-5.5 py-4 text-white bg-[color-mix(in_oklab,#0e1a22_calc(var(--nb13-p)*82%),transparent)] border-b border-b-[color-mix(in_oklab,white_calc(var(--nb13-p)*22%),transparent)] backdrop-blur-[calc(var(--nb13-p)*22px)] backdrop-saturate-[1.6] [text-shadow:0_2px_10px_rgba(0,0,0,calc(.5-var(--nb13-p)*.5))]" id="nbtw-13-bar" style="--nb13-p:0">
<a class="text-[17px] font-extrabold no-underline text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">☼ Vista</a>
<nav class="flex gap-0.5 ms-auto" aria-label="Primary">
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:opacity-100 aria-[current=page]:bg-white/[0.14]" href="#nbtw-13" aria-current="page">Stays</a>
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">Trips</a>
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">Guides</a>
</nav>
<button class="min-h-10 px-4.5 rounded-[10px] border border-white/50 bg-white/[0.18] text-white text-sm font-bold cursor-pointer backdrop-blur-[6px] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" type="button">Book</button>
</header>
<div class="h-[330px] grid place-items-center bg-[linear-gradient(180deg,rgba(0,0,0,.15),rgba(0,0,0,.4)),url('https://picsum.photos/seed/nb13/1000/700')] bg-center bg-cover">
<h2 class="px-5 text-center text-white text-[26px] font-extrabold [text-shadow:0_2px_16px_rgba(0,0,0,.5)]">Transparent over the hero</h2>
</div>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Scroll and the bar dissolves from fully transparent into frosted glass — fill, blur, border and text-shadow are all <code>calc()</code> functions of one registered <code>--nb13-p</code> property.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">There is no transition anywhere in this demo. A transition would chase a target that moves every frame; a computed value is exact at every scroll offset instead.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">The link text-shadow runs inverse to the frost: full shadow while the bar is transparent over the photograph, none once the glass has arrived.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">The handler is rAF-throttled and clamps to [0,1], because iOS overscroll reports values past both ends and an unclamped multiplier produces invalid colours.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]"><code>sticky top-0</code> keeps CLS at 0 — the bar reserves its box in flow and never pushes content as its appearance changes.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Keep scrolling to reach the fully solidified state; everything completes around the 220px mark, which is the divisor in the handler.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Now scroll back up. The bar dissolves back to nothing at exactly the same rate, because the fade has no direction of its own.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">One property, six lines of script, zero transitions — the fade is a continuous computed value, not an animation.</p>
</div>
</section><section class="nbtw-13 relative block w-full min-h-svh overflow-hidden font-nb13-sans bg-[#0c1418]" aria-label="Scroll opacity sticky navbar demo">
<div class="absolute inset-0 overflow-y-auto" id="nbtw-13-scroll">
<header class="sticky top-0 z-[6] flex flex-wrap items-center gap-x-4.5 gap-y-3 px-5.5 py-4 text-white bg-[color-mix(in_oklab,#0e1a22_calc(var(--nb13-p)*82%),transparent)] border-b border-b-[color-mix(in_oklab,white_calc(var(--nb13-p)*22%),transparent)] backdrop-blur-[calc(var(--nb13-p)*22px)] backdrop-saturate-[1.6] [text-shadow:0_2px_10px_rgba(0,0,0,calc(.5-var(--nb13-p)*.5))]" id="nbtw-13-bar" style="--nb13-p:0">
<a class="text-[17px] font-extrabold no-underline text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">☼ Vista</a>
<nav class="flex gap-0.5 ms-auto" aria-label="Primary">
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:opacity-100 aria-[current=page]:bg-white/[0.14]" href="#nbtw-13" aria-current="page">Stays</a>
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">Trips</a>
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">Guides</a>
</nav>
<button class="min-h-10 px-4.5 rounded-[10px] border border-white/50 bg-white/[0.18] text-white text-sm font-bold cursor-pointer backdrop-blur-[6px] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" type="button">Book</button>
</header>
<div class="h-[330px] grid place-items-center bg-[linear-gradient(180deg,rgba(0,0,0,.15),rgba(0,0,0,.4)),url('https://picsum.photos/seed/nb13/1000/700')] bg-center bg-cover">
<h2 class="px-5 text-center text-white text-[26px] font-extrabold [text-shadow:0_2px_16px_rgba(0,0,0,.5)]">Transparent over the hero</h2>
</div>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Scroll and the bar dissolves from fully transparent into frosted glass — fill, blur, border and text-shadow are all <code>calc()</code> functions of one registered <code>--nb13-p</code> property.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">There is no transition anywhere in this demo. A transition would chase a target that moves every frame; a computed value is exact at every scroll offset instead.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">The link text-shadow runs inverse to the frost: full shadow while the bar is transparent over the photograph, none once the glass has arrived.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">The handler is rAF-throttled and clamps to [0,1], because iOS overscroll reports values past both ends and an unclamped multiplier produces invalid colours.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]"><code>sticky top-0</code> keeps CLS at 0 — the bar reserves its box in flow and never pushes content as its appearance changes.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Keep scrolling to reach the fully solidified state; everything completes around the 220px mark, which is the divisor in the handler.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Now scroll back up. The bar dissolves back to nothing at exactly the same rate, because the fade has no direction of its own.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">One property, six lines of script, zero transitions — the fade is a continuous computed value, not an animation.</p>
</div>
</section>@import "tailwindcss";
@theme {
--font-nb13-sans: system-ui, 'Segoe UI', sans-serif;
}
/* Registering the progress value as a number is what lets the same variable
multiply into a percentage, a pixel length and an alpha. Unregistered it is
an untyped string, and calc() with a unit can fail to resolve. */
@property --nb13-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}@import "tailwindcss";
@theme {
--font-nb13-sans: system-ui, 'Segoe UI', sans-serif;
}
/* Registering the progress value as a number is what lets the same variable
multiply into a percentage, a pixel length and an alpha. Unregistered it is
an untyped string, and calc() with a unit can fail to resolve. */
@property --nb13-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}(() => {
const scroll = document.querySelector('#nbtw-13-scroll');
const bar = document.querySelector('#nbtw-13-bar');
if (!scroll || !bar) return;
let ticking = false;
scroll.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const p = Math.min(1, Math.max(0, scroll.scrollTop / 220));
bar.style.setProperty('--nb13-p', p.toFixed(3));
ticking = false;
});
}, { passive: true });
})();(() => {
const scroll = document.querySelector('#nbtw-13-scroll');
const bar = document.querySelector('#nbtw-13-bar');
if (!scroll || !bar) return;
let ticking = false;
scroll.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const p = Math.min(1, Math.max(0, scroll.scrollTop / 220));
bar.style.setProperty('--nb13-p', p.toFixed(3));
ticking = false;
});
}, { passive: true });
})();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-nb13-sans: system-ui, 'Segoe UI', sans-serif;
}
/* Registering the progress value as a number is what lets the same variable
multiply into a percentage, a pixel length and an alpha. Unregistered it is
an untyped string, and calc() with a unit can fail to resolve. */
@property --nb13-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}@import "tailwindcss";
@theme {
--font-nb13-sans: system-ui, 'Segoe UI', sans-serif;
}
/* Registering the progress value as a number is what lets the same variable
multiply into a percentage, a pixel length and an alpha. Unregistered it is
an untyped string, and calc() with a unit can fail to resolve. */
@property --nb13-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Four utilities read one variable through
calc(), so the entire fade is a function of scroll position rather than a set of states to transition between. - The demo deliberately contains no transition. Transitioning a scroll-driven value makes the bar lag behind the hero it is revealing.
@property --nb13-pwith a<number>syntax is load-bearing: the typed value is what makes multiplying it into a percentage, a length and an alpha safe.- The text-shadow is inverted from the same variable, which is how the links stay legible from photograph to frosted glass without a second state.
- The handler writes one property and nothing else — no classes, no styles, no measurement — which keeps the scroll path inside a frame.
- Compare with demo 09: a two-state
data-scrolledattribute is cheaper and needs no registration. Use this one only when the continuity is the point.
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 Scroll-Opacity Sticky Navbar
Source: https://codefronts.com/navigation/tailwind-liquid-glass-navbars/tailwind-scroll-opacity-sticky-navbar/
A navbar that starts fully transparent over a hero and dissolves into frosted glass as you scroll — continuously, not at a breakpoint. One registered custom property carries scroll progress from 0 to 1, and four utilities compute the fill, the blur, the border and the text-shadow from it with calc(). There are no transitions in the demo at all: the value is the animation.
## HTML
```html
<section class="nbtw-13 relative block w-full min-h-svh overflow-hidden font-nb13-sans bg-[#0c1418]" aria-label="Scroll opacity sticky navbar demo">
<div class="absolute inset-0 overflow-y-auto" id="nbtw-13-scroll">
<header class="sticky top-0 z-[6] flex flex-wrap items-center gap-x-4.5 gap-y-3 px-5.5 py-4 text-white bg-[color-mix(in_oklab,#0e1a22_calc(var(--nb13-p)*82%),transparent)] border-b border-b-[color-mix(in_oklab,white_calc(var(--nb13-p)*22%),transparent)] backdrop-blur-[calc(var(--nb13-p)*22px)] backdrop-saturate-[1.6] [text-shadow:0_2px_10px_rgba(0,0,0,calc(.5-var(--nb13-p)*.5))]" id="nbtw-13-bar" style="--nb13-p:0">
<a class="text-[17px] font-extrabold no-underline text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">☼ Vista</a>
<nav class="flex gap-0.5 ms-auto" aria-label="Primary">
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:opacity-100 aria-[current=page]:bg-white/[0.14]" href="#nbtw-13" aria-current="page">Stays</a>
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">Trips</a>
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">Guides</a>
</nav>
<button class="min-h-10 px-4.5 rounded-[10px] border border-white/50 bg-white/[0.18] text-white text-sm font-bold cursor-pointer backdrop-blur-[6px] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" type="button">Book</button>
</header>
<div class="h-[330px] grid place-items-center bg-[linear-gradient(180deg,rgba(0,0,0,.15),rgba(0,0,0,.4)),url('https://picsum.photos/seed/nb13/1000/700')] bg-center bg-cover">
<h2 class="px-5 text-center text-white text-[26px] font-extrabold [text-shadow:0_2px_16px_rgba(0,0,0,.5)]">Transparent over the hero</h2>
</div>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Scroll and the bar dissolves from fully transparent into frosted glass — fill, blur, border and text-shadow are all <code>calc()</code> functions of one registered <code>--nb13-p</code> property.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">There is no transition anywhere in this demo. A transition would chase a target that moves every frame; a computed value is exact at every scroll offset instead.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">The link text-shadow runs inverse to the frost: full shadow while the bar is transparent over the photograph, none once the glass has arrived.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">The handler is rAF-throttled and clamps to [0,1], because iOS overscroll reports values past both ends and an unclamped multiplier produces invalid colours.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]"><code>sticky top-0</code> keeps CLS at 0 — the bar reserves its box in flow and never pushes content as its appearance changes.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Keep scrolling to reach the fully solidified state; everything completes around the 220px mark, which is the divisor in the handler.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Now scroll back up. The bar dissolves back to nothing at exactly the same rate, because the fade has no direction of its own.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">One property, six lines of script, zero transitions — the fade is a continuous computed value, not an animation.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--font-nb13-sans: system-ui, 'Segoe UI', sans-serif;
}
/* Registering the progress value as a number is what lets the same variable
multiply into a percentage, a pixel length and an alpha. Unregistered it is
an untyped string, and calc() with a unit can fail to resolve. */
@property --nb13-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
```
## JavaScript
```js
(() => {
const scroll = document.querySelector('#nbtw-13-scroll');
const bar = document.querySelector('#nbtw-13-bar');
if (!scroll || !bar) return;
let ticking = false;
scroll.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const p = Math.min(1, Math.max(0, scroll.scrollTop / 220));
bar.style.setProperty('--nb13-p', p.toFixed(3));
ticking = false;
});
}, { passive: true });
})();
```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 Scroll-Opacity Sticky Navbar
Source: https://codefronts.com/navigation/tailwind-liquid-glass-navbars/tailwind-scroll-opacity-sticky-navbar/
A navbar that starts fully transparent over a hero and dissolves into frosted glass as you scroll — continuously, not at a breakpoint. One registered custom property carries scroll progress from 0 to 1, and four utilities compute the fill, the blur, the border and the text-shadow from it with calc(). There are no transitions in the demo at all: the value is the animation.
## HTML
```html
<section class="nbtw-13 relative block w-full min-h-svh overflow-hidden font-nb13-sans bg-[#0c1418]" aria-label="Scroll opacity sticky navbar demo">
<div class="absolute inset-0 overflow-y-auto" id="nbtw-13-scroll">
<header class="sticky top-0 z-[6] flex flex-wrap items-center gap-x-4.5 gap-y-3 px-5.5 py-4 text-white bg-[color-mix(in_oklab,#0e1a22_calc(var(--nb13-p)*82%),transparent)] border-b border-b-[color-mix(in_oklab,white_calc(var(--nb13-p)*22%),transparent)] backdrop-blur-[calc(var(--nb13-p)*22px)] backdrop-saturate-[1.6] [text-shadow:0_2px_10px_rgba(0,0,0,calc(.5-var(--nb13-p)*.5))]" id="nbtw-13-bar" style="--nb13-p:0">
<a class="text-[17px] font-extrabold no-underline text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">☼ Vista</a>
<nav class="flex gap-0.5 ms-auto" aria-label="Primary">
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:opacity-100 aria-[current=page]:bg-white/[0.14]" href="#nbtw-13" aria-current="page">Stays</a>
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">Trips</a>
<a class="flex items-center min-h-10 px-3.5 rounded-[10px] no-underline text-white opacity-90 text-[14.5px] font-semibold transition-[background-color,opacity] duration-200 hover:opacity-100 hover:bg-white/[0.14] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-13">Guides</a>
</nav>
<button class="min-h-10 px-4.5 rounded-[10px] border border-white/50 bg-white/[0.18] text-white text-sm font-bold cursor-pointer backdrop-blur-[6px] focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" type="button">Book</button>
</header>
<div class="h-[330px] grid place-items-center bg-[linear-gradient(180deg,rgba(0,0,0,.15),rgba(0,0,0,.4)),url('https://picsum.photos/seed/nb13/1000/700')] bg-center bg-cover">
<h2 class="px-5 text-center text-white text-[26px] font-extrabold [text-shadow:0_2px_16px_rgba(0,0,0,.5)]">Transparent over the hero</h2>
</div>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Scroll and the bar dissolves from fully transparent into frosted glass — fill, blur, border and text-shadow are all <code>calc()</code> functions of one registered <code>--nb13-p</code> property.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">There is no transition anywhere in this demo. A transition would chase a target that moves every frame; a computed value is exact at every scroll offset instead.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">The link text-shadow runs inverse to the frost: full shadow while the bar is transparent over the photograph, none once the glass has arrived.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">The handler is rAF-throttled and clamps to [0,1], because iOS overscroll reports values past both ends and an unclamped multiplier produces invalid colours.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]"><code>sticky top-0</code> keeps CLS at 0 — the bar reserves its box in flow and never pushes content as its appearance changes.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Keep scrolling to reach the fully solidified state; everything completes around the 220px mark, which is the divisor in the handler.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">Now scroll back up. The bar dissolves back to nothing at exactly the same rate, because the fade has no direction of its own.</p>
<p class="max-w-[46ch] mx-auto p-6 text-[15px] leading-[1.7] text-[#dfe7ec]">One property, six lines of script, zero transitions — the fade is a continuous computed value, not an animation.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--font-nb13-sans: system-ui, 'Segoe UI', sans-serif;
}
/* Registering the progress value as a number is what lets the same variable
multiply into a percentage, a pixel length and an alpha. Unregistered it is
an untyped string, and calc() with a unit can fail to resolve. */
@property --nb13-p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
```
## JavaScript
```js
(() => {
const scroll = document.querySelector('#nbtw-13-scroll');
const bar = document.querySelector('#nbtw-13-bar');
if (!scroll || !bar) return;
let ticking = false;
scroll.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const p = Math.min(1, Math.max(0, scroll.scrollTop / 220));
bar.style.setProperty('--nb13-p', p.toFixed(3));
ticking = false;
});
}, { passive: true });
})();
```How this works
One number, four consequences. The handler computes progress and writes it; the markup does the rest:
const p = Math.min(1, Math.max(0, scroll.scrollTop / 220));
bar.style.setProperty('--nb13-p', p.toFixed(3));class="bg-[color-mix(in_oklab,#0e1a22_calc(var(--nb13-p)*82%),transparent)]
border-b border-b-[color-mix(in_oklab,white_calc(var(--nb13-p)*22%),transparent)]
backdrop-blur-[calc(var(--nb13-p)*22px)]"No transitions, on purpose. A transition would fight the scroll: you would be animating toward a target that moves every frame, and the bar would lag behind the hero. Because the fade is a computed function of position, it is exact at every scroll offset and free to reverse instantly.
Registration is what makes calc() safe. @property --nb13-p { syntax: '<number>' } types the value, so multiplying it into a percentage, a pixel length and an alpha all resolve. Unregistered, it is a string, and calc() with a unit can fail silently in some engines.
The text-shadow runs the other way. At p = 0 the links sit on a photograph and need a full shadow; at p = 1 they sit on frosted glass and need none. Inverting the same variable is what keeps them legible through the whole range without a second state.
Make it yours
- Change the scroll distance over which the bar solidifies by editing the divisor (220) in the handler.
- Change the end tint by editing the colour inside the
color-mix()— a brand-dark or pure black both work; keep the 82% ceiling so it stays glass rather than a solid bar. - Cap the frost lower (
*14px) if your hero has fine detail you want to keep visible longer. - Swap the brand mark's colour at high progress by adding a second derived utility on it, reading the same variable.
- For a two-state version, drop the property and use the
data-scrolledpattern from demo 09 instead — it is cheaper and needs no registration.
Gotchas — read before shipping
- Clamp progress to [0,1] in the handler. Overscroll on iOS reports values past the ends, and an unclamped multiplier produces invalid colours.
- Register the property with
@property. Unregistered, a unitless string multiplied into a length is not guaranteed to resolve. - Do not add a transition to these utilities. It fights the scroll and makes the bar lag behind the hero it is supposed to be revealing.
- Keep the inverse text-shadow. Links on a bright photo at
p = 0fail contrast without it, and that is the state a visitor sees first. - rAF-throttle the write. A property write per scroll event is a real INP cost on mid-tier mobile.
- Arbitrary values with
calc()need underscores for their spaces, and the multiplication sign needs no escaping — but a stray space around it will end the class.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Tailwind v4 sets the floor and color-mix() is inside it. @property is what makes the typed progress value safe to multiply; where it is missing the bar still works, it just resolves less predictably at the extremes.