18 / 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 Reflective Glass Sidebar
A vertical glass sidebar that samples the colour of whatever panel is in view: scroll, and its tint, top glow and active accent shift as though the glass were picking up the light of the room behind it. The port keeps the reflected colour as a registered custom property, which is the one thing that makes the cross-fade smooth instead of a snap.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="nbtw-18 relative flex w-full min-h-svh overflow-hidden font-nb18-sans bg-[#0a0c14]" aria-label="Reflective glass sidebar demo">
<nav class="relative z-[5] flex flex-col items-center gap-2.5 w-[74px] py-4.5 overflow-hidden text-white bg-[color-mix(in_oklab,var(--nb18-reflect)_16%,rgba(255,255,255,.06))] border-e border-e-[color-mix(in_oklab,var(--nb18-reflect)_40%,rgba(255,255,255,.14))] shadow-[inset_0_1px_0_rgba(255,255,255,.3)] backdrop-blur-[24px] backdrop-saturate-[1.8] transition-[background-color,border-color] duration-500 motion-reduce:transition-none" id="nbtw-18-side" aria-label="Primary">
<span class="absolute inset-x-0 top-0 h-30 pointer-events-none opacity-60 bg-[radial-gradient(60px_120px_at_50%_0,color-mix(in_oklab,var(--nb18-reflect)_80%,transparent),transparent_70%)] transition-[background] duration-500 motion-reduce:transition-none" aria-hidden="true"></span>
<span class="relative z-[1] mb-2 text-[22px] text-[color-mix(in_oklab,var(--nb18-reflect)_60%,white)] transition-colors duration-500" aria-hidden="true">◈</span>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:text-white aria-[current=page]:bg-[color-mix(in_oklab,var(--nb18-reflect)_55%,transparent)] aria-[current=page]:shadow-[0_0_18px_-2px_color-mix(in_oklab,var(--nb18-reflect)_70%,transparent)]" href="#nbtw-18" aria-current="page" aria-label="Home">⌂</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Discover">◎</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Media">▤</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Settings">⚙</a>
</nav>
<div class="flex-1 h-svh overflow-y-auto snap-y snap-mandatory" id="nbtw-18-scroll">
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#5b8cff_55%,#0a0c14),#0a0c14_75%)]" data-color="#5b8cff"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Ocean</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#ff5ea8_55%,#0a0c14),#0a0c14_75%)]" data-color="#ff5ea8"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Bloom</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#38e8c6_55%,#0a0c14),#0a0c14_75%)]" data-color="#38e8c6"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Mint</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#ffb03a_55%,#0a0c14),#0a0c14_75%)]" data-color="#ffb03a"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Amber</h3></div>
</div>
</section><section class="nbtw-18 relative flex w-full min-h-svh overflow-hidden font-nb18-sans bg-[#0a0c14]" aria-label="Reflective glass sidebar demo">
<nav class="relative z-[5] flex flex-col items-center gap-2.5 w-[74px] py-4.5 overflow-hidden text-white bg-[color-mix(in_oklab,var(--nb18-reflect)_16%,rgba(255,255,255,.06))] border-e border-e-[color-mix(in_oklab,var(--nb18-reflect)_40%,rgba(255,255,255,.14))] shadow-[inset_0_1px_0_rgba(255,255,255,.3)] backdrop-blur-[24px] backdrop-saturate-[1.8] transition-[background-color,border-color] duration-500 motion-reduce:transition-none" id="nbtw-18-side" aria-label="Primary">
<span class="absolute inset-x-0 top-0 h-30 pointer-events-none opacity-60 bg-[radial-gradient(60px_120px_at_50%_0,color-mix(in_oklab,var(--nb18-reflect)_80%,transparent),transparent_70%)] transition-[background] duration-500 motion-reduce:transition-none" aria-hidden="true"></span>
<span class="relative z-[1] mb-2 text-[22px] text-[color-mix(in_oklab,var(--nb18-reflect)_60%,white)] transition-colors duration-500" aria-hidden="true">◈</span>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:text-white aria-[current=page]:bg-[color-mix(in_oklab,var(--nb18-reflect)_55%,transparent)] aria-[current=page]:shadow-[0_0_18px_-2px_color-mix(in_oklab,var(--nb18-reflect)_70%,transparent)]" href="#nbtw-18" aria-current="page" aria-label="Home">⌂</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Discover">◎</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Media">▤</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Settings">⚙</a>
</nav>
<div class="flex-1 h-svh overflow-y-auto snap-y snap-mandatory" id="nbtw-18-scroll">
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#5b8cff_55%,#0a0c14),#0a0c14_75%)]" data-color="#5b8cff"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Ocean</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#ff5ea8_55%,#0a0c14),#0a0c14_75%)]" data-color="#ff5ea8"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Bloom</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#38e8c6_55%,#0a0c14),#0a0c14_75%)]" data-color="#38e8c6"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Mint</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#ffb03a_55%,#0a0c14),#0a0c14_75%)]" data-color="#ffb03a"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Amber</h3></div>
</div>
</section>@import "tailwindcss";
@theme {
--font-nb18-sans: system-ui, 'Segoe UI', sans-serif;
}
/* The load-bearing rule of this demo. An unregistered custom property holding
a colour is an untyped string: a new value swaps instantly and no transition
can catch it. Registered as <color>, the browser interpolates. */
@property --nb18-reflect {
syntax: '<color>';
inherits: true;
initial-value: #5b8cff;
}@import "tailwindcss";
@theme {
--font-nb18-sans: system-ui, 'Segoe UI', sans-serif;
}
/* The load-bearing rule of this demo. An unregistered custom property holding
a colour is an untyped string: a new value swaps instantly and no transition
can catch it. Registered as <color>, the browser interpolates. */
@property --nb18-reflect {
syntax: '<color>';
inherits: true;
initial-value: #5b8cff;
}(() => {
const side = document.querySelector('#nbtw-18-side');
const scroll = document.querySelector('#nbtw-18-scroll');
if (!side || !scroll) return;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) side.style.setProperty('--nb18-reflect', e.target.dataset.color);
});
}, { root: scroll, threshold: 0.6 });
scroll.querySelectorAll('[data-color]').forEach((p) => io.observe(p));
})();(() => {
const side = document.querySelector('#nbtw-18-side');
const scroll = document.querySelector('#nbtw-18-scroll');
if (!side || !scroll) return;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) side.style.setProperty('--nb18-reflect', e.target.dataset.color);
});
}, { root: scroll, threshold: 0.6 });
scroll.querySelectorAll('[data-color]').forEach((p) => io.observe(p));
})();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-nb18-sans: system-ui, 'Segoe UI', sans-serif;
}
/* The load-bearing rule of this demo. An unregistered custom property holding
a colour is an untyped string: a new value swaps instantly and no transition
can catch it. Registered as <color>, the browser interpolates. */
@property --nb18-reflect {
syntax: '<color>';
inherits: true;
initial-value: #5b8cff;
}@import "tailwindcss";
@theme {
--font-nb18-sans: system-ui, 'Segoe UI', sans-serif;
}
/* The load-bearing rule of this demo. An unregistered custom property holding
a colour is an untyped string: a new value swaps instantly and no transition
can catch it. Registered as <color>, the browser interpolates. */
@property --nb18-reflect {
syntax: '<color>';
inherits: true;
initial-value: #5b8cff;
}What changed in the port
Which parts became utilities, which stayed CSS, and why.
@propertywithsyntax: '<color>'is the whole demo. Unregistered, the reflected colour would snap on every panel change and no transition could smooth it.- Five utilities across three elements all derive from
var(--nb18-reflect)throughcolor-mix(), so one property write re-tints the fill, the border, the glow, the logo and the active item together. inherits: truematters because the derived utilities sit on descendants of the element the property is written to.- The observer replaces scroll maths entirely: four lines, no layout reads, and a threshold that reports the panel actually in view.
- Mix percentages are kept low (16% fill, 55% active) so no panel colour can push the white icons below a readable contrast ratio.
snap-y snap-mandatorygives each colour a settled moment, which is what makes the reflection read as sampling a room rather than smearing between two.
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 Reflective Glass Sidebar
Source: https://codefronts.com/navigation/tailwind-liquid-glass-navbars/tailwind-reflective-glass-sidebar/
A vertical glass sidebar that samples the colour of whatever panel is in view: scroll, and its tint, top glow and active accent shift as though the glass were picking up the light of the room behind it. The port keeps the reflected colour as a registered custom property, which is the one thing that makes the cross-fade smooth instead of a snap.
## HTML
```html
<section class="nbtw-18 relative flex w-full min-h-svh overflow-hidden font-nb18-sans bg-[#0a0c14]" aria-label="Reflective glass sidebar demo">
<nav class="relative z-[5] flex flex-col items-center gap-2.5 w-[74px] py-4.5 overflow-hidden text-white bg-[color-mix(in_oklab,var(--nb18-reflect)_16%,rgba(255,255,255,.06))] border-e border-e-[color-mix(in_oklab,var(--nb18-reflect)_40%,rgba(255,255,255,.14))] shadow-[inset_0_1px_0_rgba(255,255,255,.3)] backdrop-blur-[24px] backdrop-saturate-[1.8] transition-[background-color,border-color] duration-500 motion-reduce:transition-none" id="nbtw-18-side" aria-label="Primary">
<span class="absolute inset-x-0 top-0 h-30 pointer-events-none opacity-60 bg-[radial-gradient(60px_120px_at_50%_0,color-mix(in_oklab,var(--nb18-reflect)_80%,transparent),transparent_70%)] transition-[background] duration-500 motion-reduce:transition-none" aria-hidden="true"></span>
<span class="relative z-[1] mb-2 text-[22px] text-[color-mix(in_oklab,var(--nb18-reflect)_60%,white)] transition-colors duration-500" aria-hidden="true">◈</span>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:text-white aria-[current=page]:bg-[color-mix(in_oklab,var(--nb18-reflect)_55%,transparent)] aria-[current=page]:shadow-[0_0_18px_-2px_color-mix(in_oklab,var(--nb18-reflect)_70%,transparent)]" href="#nbtw-18" aria-current="page" aria-label="Home">⌂</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Discover">◎</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Media">▤</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Settings">⚙</a>
</nav>
<div class="flex-1 h-svh overflow-y-auto snap-y snap-mandatory" id="nbtw-18-scroll">
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#5b8cff_55%,#0a0c14),#0a0c14_75%)]" data-color="#5b8cff"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Ocean</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#ff5ea8_55%,#0a0c14),#0a0c14_75%)]" data-color="#ff5ea8"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Bloom</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#38e8c6_55%,#0a0c14),#0a0c14_75%)]" data-color="#38e8c6"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Mint</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#ffb03a_55%,#0a0c14),#0a0c14_75%)]" data-color="#ffb03a"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Amber</h3></div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--font-nb18-sans: system-ui, 'Segoe UI', sans-serif;
}
/* The load-bearing rule of this demo. An unregistered custom property holding
a colour is an untyped string: a new value swaps instantly and no transition
can catch it. Registered as <color>, the browser interpolates. */
@property --nb18-reflect {
syntax: '<color>';
inherits: true;
initial-value: #5b8cff;
}
```
## JavaScript
```js
(() => {
const side = document.querySelector('#nbtw-18-side');
const scroll = document.querySelector('#nbtw-18-scroll');
if (!side || !scroll) return;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) side.style.setProperty('--nb18-reflect', e.target.dataset.color);
});
}, { root: scroll, threshold: 0.6 });
scroll.querySelectorAll('[data-color]').forEach((p) => io.observe(p));
})();
```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 Reflective Glass Sidebar
Source: https://codefronts.com/navigation/tailwind-liquid-glass-navbars/tailwind-reflective-glass-sidebar/
A vertical glass sidebar that samples the colour of whatever panel is in view: scroll, and its tint, top glow and active accent shift as though the glass were picking up the light of the room behind it. The port keeps the reflected colour as a registered custom property, which is the one thing that makes the cross-fade smooth instead of a snap.
## HTML
```html
<section class="nbtw-18 relative flex w-full min-h-svh overflow-hidden font-nb18-sans bg-[#0a0c14]" aria-label="Reflective glass sidebar demo">
<nav class="relative z-[5] flex flex-col items-center gap-2.5 w-[74px] py-4.5 overflow-hidden text-white bg-[color-mix(in_oklab,var(--nb18-reflect)_16%,rgba(255,255,255,.06))] border-e border-e-[color-mix(in_oklab,var(--nb18-reflect)_40%,rgba(255,255,255,.14))] shadow-[inset_0_1px_0_rgba(255,255,255,.3)] backdrop-blur-[24px] backdrop-saturate-[1.8] transition-[background-color,border-color] duration-500 motion-reduce:transition-none" id="nbtw-18-side" aria-label="Primary">
<span class="absolute inset-x-0 top-0 h-30 pointer-events-none opacity-60 bg-[radial-gradient(60px_120px_at_50%_0,color-mix(in_oklab,var(--nb18-reflect)_80%,transparent),transparent_70%)] transition-[background] duration-500 motion-reduce:transition-none" aria-hidden="true"></span>
<span class="relative z-[1] mb-2 text-[22px] text-[color-mix(in_oklab,var(--nb18-reflect)_60%,white)] transition-colors duration-500" aria-hidden="true">◈</span>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:text-white aria-[current=page]:bg-[color-mix(in_oklab,var(--nb18-reflect)_55%,transparent)] aria-[current=page]:shadow-[0_0_18px_-2px_color-mix(in_oklab,var(--nb18-reflect)_70%,transparent)]" href="#nbtw-18" aria-current="page" aria-label="Home">⌂</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Discover">◎</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Media">▤</a>
<a class="relative z-[1] grid place-items-center size-[46px] rounded-[14px] no-underline text-white/80 text-[19px] transition-[background-color,color] duration-300 hover:bg-white/[0.12] hover:text-white focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2" href="#nbtw-18" aria-label="Settings">⚙</a>
</nav>
<div class="flex-1 h-svh overflow-y-auto snap-y snap-mandatory" id="nbtw-18-scroll">
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#5b8cff_55%,#0a0c14),#0a0c14_75%)]" data-color="#5b8cff"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Ocean</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#ff5ea8_55%,#0a0c14),#0a0c14_75%)]" data-color="#ff5ea8"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Bloom</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#38e8c6_55%,#0a0c14),#0a0c14_75%)]" data-color="#38e8c6"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Mint</h3></div>
<div class="h-svh min-h-130 snap-start grid place-items-center bg-[radial-gradient(circle_at_50%_40%,color-mix(in_oklab,#ffb03a_55%,#0a0c14),#0a0c14_75%)]" data-color="#ffb03a"><h3 class="text-white text-[32px] font-extrabold tracking-[-0.02em] [text-shadow:0_2px_20px_rgba(0,0,0,.4)]">Amber</h3></div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--font-nb18-sans: system-ui, 'Segoe UI', sans-serif;
}
/* The load-bearing rule of this demo. An unregistered custom property holding
a colour is an untyped string: a new value swaps instantly and no transition
can catch it. Registered as <color>, the browser interpolates. */
@property --nb18-reflect {
syntax: '<color>';
inherits: true;
initial-value: #5b8cff;
}
```
## JavaScript
```js
(() => {
const side = document.querySelector('#nbtw-18-side');
const scroll = document.querySelector('#nbtw-18-scroll');
if (!side || !scroll) return;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) side.style.setProperty('--nb18-reflect', e.target.dataset.color);
});
}, { root: scroll, threshold: 0.6 });
scroll.querySelectorAll('[data-color]').forEach((p) => io.observe(p));
})();
```How this works
Registration is the whole demo. An unregistered custom property holding a colour is an untyped string: assigning a new value swaps it instantly and no transition can catch it. Registered as a colour, the browser interpolates:
@property --nb18-reflect {
syntax: '<color>';
inherits: true;
initial-value: #5b8cff;
}Four utilities read one property. The sidebar's fill, its trailing border, the top glow layer and the active item's background and halo are all color-mix() derivations of var(--nb18-reflect), so a single write re-tints the entire component coherently.
Observation beats measurement. An IntersectionObserver on the panels reports which one is centred at a 0.6 threshold, with no scroll listener and no layout reads. The handler is four lines and does nothing but setProperty.
Contrast has to hold across every colour. The icons stay white with their own opacity ramp, and the reflected colour is mixed at 16% for the fill and 55% for the active item — low enough that no panel colour can push the icons below a readable ratio.
Make it yours
- Feed real dominant colours in — extract from an image on load and write the same property; nothing else changes.
- Strengthen the reflection by raising the mix percentages in the fill and glow; keep an eye on icon contrast as you do.
- Add a moving highlight that also reads
--nb18-reflectfor a sense of light travelling across the pane. - Switch to horizontal by making the sidebar a top bar; the glow layer's gradient is the only thing that needs reorienting.
- Lengthen the cross-fade with
duration-700for a slower, more liquid sample.
Gotchas — read before shipping
- Register the property as
<color>or the tint snaps. This is the entire difference between a reflective sidebar and a flickering one. - Use an IntersectionObserver, not scroll maths. Scroll-derived section detection is both less accurate and more expensive.
- Keep text and icon contrast valid across every reflected colour, including the brightest panel — test the worst case, not the default.
- The glow layer must be
pointer-events-none; it covers the top of the sidebar including the logo area. inherits: trueon the registration matters, because the derived utilities live on descendants of the element the property is written to.- A scroll-snap container with panels this tall needs a real height; without it the observer thresholds never resolve cleanly.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Tailwind v4 sets the floor, and @property support (Chrome 85+, Safari 16.4+, Firefox 128+) sits inside it. Without JS the sidebar keeps its initial tint and stays fully usable.