01 / 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 iOS 26 Floating Tab Bar
The iOS 26 bottom tab bar as a floating glass capsule, ported so the collapse is a state rather than a class. The scroll handler writes data-compact on the bar and nothing else; data-[compact=true]: retunes the bar's own padding while group-data-[compact=true]: reaches the labels two levels down. The frost recipe — translucent fill, hairline rim, inset specular, backdrop-blur-[24px] — is one class list you can read end to end.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="nbtw-01 relative block w-full min-h-svh overflow-hidden font-nb01-sans bg-[conic-gradient(from_210deg_at_30%_20%,#5b8cff,#b06bff,#ff7ab0,#5b8cff)] bg-[length:200%_200%] animate-nb01-bg motion-reduce:animate-none" aria-label="iOS 26 floating tab bar demo">
<div class="absolute inset-0 overflow-y-auto px-6 pt-8 pb-35 text-white" id="nbtw-01-scroll">
<h2 class="text-[26px] font-extrabold tracking-[-0.02em] mb-3.5 [text-shadow:0_2px_12px_rgba(0,0,0,.3)]">Scroll me ↓</h2>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">The floating tab bar shrinks to icons as you scroll down and springs back on scroll-up, idle or hover — the whole collapse is one <code>data-compact</code> attribute.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Keep scrolling to watch the glass capsule frost the content passing beneath it. The bar collapses on scroll-down, expands on scroll-up, and always expands after 600ms of idle so it stays reachable.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Every tab is a real link with a 44px hit target and an <code>aria-current</code> active state, and the active pill is styled from that attribute with <code>aria-[current=page]:</code>.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Labels stay in the DOM when collapsed — <code>group-data-[compact=true]:max-w-0</code> only clamps their width — so screen readers still announce them.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Hovering the bar or focusing any tab forces the expanded state, which is what keeps a keyboard user from being stranded in the icon-only bar.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">All motion is padding, max-width, opacity and backdrop-filter — no layout thrashing on the page itself, INP-friendly, WCAG 2.2 compliant.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Keep going to see the bar frost this line…</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">…and this one too. That's it — scroll up to reset, or hover the bar to force it open.</p>
</div>
<nav class="group absolute left-1/2 bottom-[22px] -translate-x-1/2 flex gap-1.5 px-3.5 py-2.5 rounded-full isolate bg-[color-mix(in_oklab,white_16%,transparent)] border border-white/40 shadow-[inset_0_1px_0_rgba(255,255,255,.55),0_20px_44px_-20px_rgba(0,0,0,.6)] backdrop-blur-[24px] backdrop-saturate-[1.8] transition-[padding,gap] duration-[450ms] ease-nb01 data-[compact=true]:gap-0.5 data-[compact=true]:p-2 motion-reduce:transition-none" id="nbtw-01-bar" data-compact="false" aria-label="Primary">
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:bg-nb01-accent aria-[current=page]:shadow-[0_6px_16px_-6px_color-mix(in_oklab,var(--color-nb01-accent)_70%,transparent)] aria-[current=page]:hover:bg-nb01-accent group-data-[compact=true]:px-3" href="#nbtw-01" aria-current="page" aria-label="Home">
<span class="text-[19px] leading-none" aria-hidden="true">⌂</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Home</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Search">
<span class="text-[19px] leading-none" aria-hidden="true">⌕</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Search</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Saved">
<span class="text-[19px] leading-none" aria-hidden="true">♥</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Saved</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Profile">
<span class="text-[19px] leading-none" aria-hidden="true">◉</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Profile</span>
</a>
</nav>
</section><section class="nbtw-01 relative block w-full min-h-svh overflow-hidden font-nb01-sans bg-[conic-gradient(from_210deg_at_30%_20%,#5b8cff,#b06bff,#ff7ab0,#5b8cff)] bg-[length:200%_200%] animate-nb01-bg motion-reduce:animate-none" aria-label="iOS 26 floating tab bar demo">
<div class="absolute inset-0 overflow-y-auto px-6 pt-8 pb-35 text-white" id="nbtw-01-scroll">
<h2 class="text-[26px] font-extrabold tracking-[-0.02em] mb-3.5 [text-shadow:0_2px_12px_rgba(0,0,0,.3)]">Scroll me ↓</h2>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">The floating tab bar shrinks to icons as you scroll down and springs back on scroll-up, idle or hover — the whole collapse is one <code>data-compact</code> attribute.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Keep scrolling to watch the glass capsule frost the content passing beneath it. The bar collapses on scroll-down, expands on scroll-up, and always expands after 600ms of idle so it stays reachable.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Every tab is a real link with a 44px hit target and an <code>aria-current</code> active state, and the active pill is styled from that attribute with <code>aria-[current=page]:</code>.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Labels stay in the DOM when collapsed — <code>group-data-[compact=true]:max-w-0</code> only clamps their width — so screen readers still announce them.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Hovering the bar or focusing any tab forces the expanded state, which is what keeps a keyboard user from being stranded in the icon-only bar.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">All motion is padding, max-width, opacity and backdrop-filter — no layout thrashing on the page itself, INP-friendly, WCAG 2.2 compliant.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Keep going to see the bar frost this line…</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">…and this one too. That's it — scroll up to reset, or hover the bar to force it open.</p>
</div>
<nav class="group absolute left-1/2 bottom-[22px] -translate-x-1/2 flex gap-1.5 px-3.5 py-2.5 rounded-full isolate bg-[color-mix(in_oklab,white_16%,transparent)] border border-white/40 shadow-[inset_0_1px_0_rgba(255,255,255,.55),0_20px_44px_-20px_rgba(0,0,0,.6)] backdrop-blur-[24px] backdrop-saturate-[1.8] transition-[padding,gap] duration-[450ms] ease-nb01 data-[compact=true]:gap-0.5 data-[compact=true]:p-2 motion-reduce:transition-none" id="nbtw-01-bar" data-compact="false" aria-label="Primary">
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:bg-nb01-accent aria-[current=page]:shadow-[0_6px_16px_-6px_color-mix(in_oklab,var(--color-nb01-accent)_70%,transparent)] aria-[current=page]:hover:bg-nb01-accent group-data-[compact=true]:px-3" href="#nbtw-01" aria-current="page" aria-label="Home">
<span class="text-[19px] leading-none" aria-hidden="true">⌂</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Home</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Search">
<span class="text-[19px] leading-none" aria-hidden="true">⌕</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Search</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Saved">
<span class="text-[19px] leading-none" aria-hidden="true">♥</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Saved</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Profile">
<span class="text-[19px] leading-none" aria-hidden="true">◉</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Profile</span>
</a>
</nav>
</section>@import "tailwindcss";
@theme {
--color-nb01-accent: #6b73f5;
--color-nb01-accent: oklch(0.64 0.2 265);
--font-nb01-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--ease-nb01: cubic-bezier(0.34, 1.4, 0.5, 1);
--animate-nb01-bg: nb01-bg 18s ease-in-out infinite;
}
/* Keyframes cannot be a theme entry on their own — the @theme --animate-* token
names the animation, this declares what it does. */
@keyframes nb01-bg {
50% {
background-position: 100% 100%;
}
}@import "tailwindcss";
@theme {
--color-nb01-accent: #6b73f5;
--color-nb01-accent: oklch(0.64 0.2 265);
--font-nb01-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--ease-nb01: cubic-bezier(0.34, 1.4, 0.5, 1);
--animate-nb01-bg: nb01-bg 18s ease-in-out infinite;
}
/* Keyframes cannot be a theme entry on their own — the @theme --animate-* token
names the animation, this declares what it does. */
@keyframes nb01-bg {
50% {
background-position: 100% 100%;
}
}(() => {
const scroll = document.querySelector('#nbtw-01-scroll');
const bar = document.querySelector('#nbtw-01-bar');
if (!scroll || !bar) return;
const THRESHOLD = 10;
let last = 0, idle;
const set = (v) => { bar.dataset.compact = String(v); };
scroll.addEventListener('scroll', () => {
const y = scroll.scrollTop;
if (y > last + THRESHOLD && y > 40) set(true);
else if (y < last - THRESHOLD) set(false);
last = y;
clearTimeout(idle);
idle = setTimeout(() => set(false), 600);
}, { passive: true });
bar.addEventListener('pointerenter', () => set(false));
bar.addEventListener('focusin', () => set(false));
})();(() => {
const scroll = document.querySelector('#nbtw-01-scroll');
const bar = document.querySelector('#nbtw-01-bar');
if (!scroll || !bar) return;
const THRESHOLD = 10;
let last = 0, idle;
const set = (v) => { bar.dataset.compact = String(v); };
scroll.addEventListener('scroll', () => {
const y = scroll.scrollTop;
if (y > last + THRESHOLD && y > 40) set(true);
else if (y < last - THRESHOLD) set(false);
last = y;
clearTimeout(idle);
idle = setTimeout(() => set(false), 600);
}, { passive: true });
bar.addEventListener('pointerenter', () => set(false));
bar.addEventListener('focusin', () => set(false));
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-nb01-accent: #6b73f5;
--color-nb01-accent: oklch(0.64 0.2 265);
--font-nb01-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--ease-nb01: cubic-bezier(0.34, 1.4, 0.5, 1);
--animate-nb01-bg: nb01-bg 18s ease-in-out infinite;
}
/* Keyframes cannot be a theme entry on their own — the @theme --animate-* token
names the animation, this declares what it does. */
@keyframes nb01-bg {
50% {
background-position: 100% 100%;
}
}@import "tailwindcss";
@theme {
--color-nb01-accent: #6b73f5;
--color-nb01-accent: oklch(0.64 0.2 265);
--font-nb01-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--ease-nb01: cubic-bezier(0.34, 1.4, 0.5, 1);
--animate-nb01-bg: nb01-bg 18s ease-in-out infinite;
}
/* Keyframes cannot be a theme entry on their own — the @theme --animate-* token
names the animation, this declares what it does. */
@keyframes nb01-bg {
50% {
background-position: 100% 100%;
}
}What changed in the port
Which parts became utilities, which stayed CSS, and why.
- No font
<link>here on purpose: this is OS chrome, so the stack is the system UI font declared once as--font-nb01-sans. Where a demo does need a webfont, load it as a<link>insetup— Tailwind emits user@importrules after its own@layerblocks, where the browser drops them. - The class toggle becomes an attribute write.
bar.dataset.compact = 'true'is the whole state layer, and every visual consequence is typed in the markup as adata-[compact=true]:orgroup-data-[compact=true]:utility. - Three CSS rules collapse into six utilities.
.is-compact,.is-compact .nb-01__lbland.is-compact .nb-01__tabno longer exist as selectors at all. - The active pill reads
aria-[current=page]:rather than a class, so the highlight and the accessible state cannot drift apart. - The drifting conic mesh is an
--animate-*theme entry, which is what lets the root sayanimate-nb01-bginstead of carrying an animation shorthand. motion-reduce:sits next to each transition it calms, so the reduced-motion story is auditable by reading the element.
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 iOS 26 Floating Tab Bar
Source: https://codefronts.com/navigation/tailwind-liquid-glass-navbars/tailwind-ios-26-floating-tab-bar/
The iOS 26 bottom tab bar as a floating glass capsule, ported so the collapse is a state rather than a class. The scroll handler writes data-compact on the bar and nothing else; data-[compact=true]: retunes the bar's own padding while group-data-[compact=true]: reaches the labels two levels down. The frost recipe — translucent fill, hairline rim, inset specular, backdrop-blur-[24px] — is one class list you can read end to end.
## HTML
```html
<section class="nbtw-01 relative block w-full min-h-svh overflow-hidden font-nb01-sans bg-[conic-gradient(from_210deg_at_30%_20%,#5b8cff,#b06bff,#ff7ab0,#5b8cff)] bg-[length:200%_200%] animate-nb01-bg motion-reduce:animate-none" aria-label="iOS 26 floating tab bar demo">
<div class="absolute inset-0 overflow-y-auto px-6 pt-8 pb-35 text-white" id="nbtw-01-scroll">
<h2 class="text-[26px] font-extrabold tracking-[-0.02em] mb-3.5 [text-shadow:0_2px_12px_rgba(0,0,0,.3)]">Scroll me ↓</h2>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">The floating tab bar shrinks to icons as you scroll down and springs back on scroll-up, idle or hover — the whole collapse is one <code>data-compact</code> attribute.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Keep scrolling to watch the glass capsule frost the content passing beneath it. The bar collapses on scroll-down, expands on scroll-up, and always expands after 600ms of idle so it stays reachable.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Every tab is a real link with a 44px hit target and an <code>aria-current</code> active state, and the active pill is styled from that attribute with <code>aria-[current=page]:</code>.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Labels stay in the DOM when collapsed — <code>group-data-[compact=true]:max-w-0</code> only clamps their width — so screen readers still announce them.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Hovering the bar or focusing any tab forces the expanded state, which is what keeps a keyboard user from being stranded in the icon-only bar.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">All motion is padding, max-width, opacity and backdrop-filter — no layout thrashing on the page itself, INP-friendly, WCAG 2.2 compliant.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Keep going to see the bar frost this line…</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">…and this one too. That's it — scroll up to reset, or hover the bar to force it open.</p>
</div>
<nav class="group absolute left-1/2 bottom-[22px] -translate-x-1/2 flex gap-1.5 px-3.5 py-2.5 rounded-full isolate bg-[color-mix(in_oklab,white_16%,transparent)] border border-white/40 shadow-[inset_0_1px_0_rgba(255,255,255,.55),0_20px_44px_-20px_rgba(0,0,0,.6)] backdrop-blur-[24px] backdrop-saturate-[1.8] transition-[padding,gap] duration-[450ms] ease-nb01 data-[compact=true]:gap-0.5 data-[compact=true]:p-2 motion-reduce:transition-none" id="nbtw-01-bar" data-compact="false" aria-label="Primary">
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:bg-nb01-accent aria-[current=page]:shadow-[0_6px_16px_-6px_color-mix(in_oklab,var(--color-nb01-accent)_70%,transparent)] aria-[current=page]:hover:bg-nb01-accent group-data-[compact=true]:px-3" href="#nbtw-01" aria-current="page" aria-label="Home">
<span class="text-[19px] leading-none" aria-hidden="true">⌂</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Home</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Search">
<span class="text-[19px] leading-none" aria-hidden="true">⌕</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Search</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Saved">
<span class="text-[19px] leading-none" aria-hidden="true">♥</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Saved</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Profile">
<span class="text-[19px] leading-none" aria-hidden="true">◉</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Profile</span>
</a>
</nav>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-nb01-accent: #6b73f5;
--color-nb01-accent: oklch(0.64 0.2 265);
--font-nb01-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--ease-nb01: cubic-bezier(0.34, 1.4, 0.5, 1);
--animate-nb01-bg: nb01-bg 18s ease-in-out infinite;
}
/* Keyframes cannot be a theme entry on their own — the @theme --animate-* token
names the animation, this declares what it does. */
@keyframes nb01-bg {
50% {
background-position: 100% 100%;
}
}
```
## JavaScript
```js
(() => {
const scroll = document.querySelector('#nbtw-01-scroll');
const bar = document.querySelector('#nbtw-01-bar');
if (!scroll || !bar) return;
const THRESHOLD = 10;
let last = 0, idle;
const set = (v) => { bar.dataset.compact = String(v); };
scroll.addEventListener('scroll', () => {
const y = scroll.scrollTop;
if (y > last + THRESHOLD && y > 40) set(true);
else if (y < last - THRESHOLD) set(false);
last = y;
clearTimeout(idle);
idle = setTimeout(() => set(false), 600);
}, { passive: true });
bar.addEventListener('pointerenter', () => set(false));
bar.addEventListener('focusin', () => set(false));
})();
```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 iOS 26 Floating Tab Bar
Source: https://codefronts.com/navigation/tailwind-liquid-glass-navbars/tailwind-ios-26-floating-tab-bar/
The iOS 26 bottom tab bar as a floating glass capsule, ported so the collapse is a state rather than a class. The scroll handler writes data-compact on the bar and nothing else; data-[compact=true]: retunes the bar's own padding while group-data-[compact=true]: reaches the labels two levels down. The frost recipe — translucent fill, hairline rim, inset specular, backdrop-blur-[24px] — is one class list you can read end to end.
## HTML
```html
<section class="nbtw-01 relative block w-full min-h-svh overflow-hidden font-nb01-sans bg-[conic-gradient(from_210deg_at_30%_20%,#5b8cff,#b06bff,#ff7ab0,#5b8cff)] bg-[length:200%_200%] animate-nb01-bg motion-reduce:animate-none" aria-label="iOS 26 floating tab bar demo">
<div class="absolute inset-0 overflow-y-auto px-6 pt-8 pb-35 text-white" id="nbtw-01-scroll">
<h2 class="text-[26px] font-extrabold tracking-[-0.02em] mb-3.5 [text-shadow:0_2px_12px_rgba(0,0,0,.3)]">Scroll me ↓</h2>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">The floating tab bar shrinks to icons as you scroll down and springs back on scroll-up, idle or hover — the whole collapse is one <code>data-compact</code> attribute.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Keep scrolling to watch the glass capsule frost the content passing beneath it. The bar collapses on scroll-down, expands on scroll-up, and always expands after 600ms of idle so it stays reachable.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Every tab is a real link with a 44px hit target and an <code>aria-current</code> active state, and the active pill is styled from that attribute with <code>aria-[current=page]:</code>.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Labels stay in the DOM when collapsed — <code>group-data-[compact=true]:max-w-0</code> only clamps their width — so screen readers still announce them.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Hovering the bar or focusing any tab forces the expanded state, which is what keeps a keyboard user from being stranded in the icon-only bar.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">All motion is padding, max-width, opacity and backdrop-filter — no layout thrashing on the page itself, INP-friendly, WCAG 2.2 compliant.</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">Keep going to see the bar frost this line…</p>
<p class="max-w-[44ch] mb-16 text-[15px] leading-[1.7] text-white/90 [text-shadow:0_1px_8px_rgba(0,0,0,.28)]">…and this one too. That's it — scroll up to reset, or hover the bar to force it open.</p>
</div>
<nav class="group absolute left-1/2 bottom-[22px] -translate-x-1/2 flex gap-1.5 px-3.5 py-2.5 rounded-full isolate bg-[color-mix(in_oklab,white_16%,transparent)] border border-white/40 shadow-[inset_0_1px_0_rgba(255,255,255,.55),0_20px_44px_-20px_rgba(0,0,0,.6)] backdrop-blur-[24px] backdrop-saturate-[1.8] transition-[padding,gap] duration-[450ms] ease-nb01 data-[compact=true]:gap-0.5 data-[compact=true]:p-2 motion-reduce:transition-none" id="nbtw-01-bar" data-compact="false" aria-label="Primary">
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 aria-[current=page]:bg-nb01-accent aria-[current=page]:shadow-[0_6px_16px_-6px_color-mix(in_oklab,var(--color-nb01-accent)_70%,transparent)] aria-[current=page]:hover:bg-nb01-accent group-data-[compact=true]:px-3" href="#nbtw-01" aria-current="page" aria-label="Home">
<span class="text-[19px] leading-none" aria-hidden="true">⌂</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Home</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Search">
<span class="text-[19px] leading-none" aria-hidden="true">⌕</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Search</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Saved">
<span class="text-[19px] leading-none" aria-hidden="true">♥</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Saved</span>
</a>
<a class="flex items-center gap-2 min-h-11 px-4 rounded-full no-underline text-white text-sm font-semibold transition-[background-color,transform] duration-200 hover:bg-white/15 focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-2 group-data-[compact=true]:px-3" href="#nbtw-01" aria-label="Profile">
<span class="text-[19px] leading-none" aria-hidden="true">◉</span>
<span class="max-w-20 overflow-hidden whitespace-nowrap transition-[max-width,opacity] duration-[450ms] ease-nb01 group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0 motion-reduce:transition-none">Profile</span>
</a>
</nav>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-nb01-accent: #6b73f5;
--color-nb01-accent: oklch(0.64 0.2 265);
--font-nb01-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--ease-nb01: cubic-bezier(0.34, 1.4, 0.5, 1);
--animate-nb01-bg: nb01-bg 18s ease-in-out infinite;
}
/* Keyframes cannot be a theme entry on their own — the @theme --animate-* token
names the animation, this declares what it does. */
@keyframes nb01-bg {
50% {
background-position: 100% 100%;
}
}
```
## JavaScript
```js
(() => {
const scroll = document.querySelector('#nbtw-01-scroll');
const bar = document.querySelector('#nbtw-01-bar');
if (!scroll || !bar) return;
const THRESHOLD = 10;
let last = 0, idle;
const set = (v) => { bar.dataset.compact = String(v); };
scroll.addEventListener('scroll', () => {
const y = scroll.scrollTop;
if (y > last + THRESHOLD && y > 40) set(true);
else if (y < last - THRESHOLD) set(false);
last = y;
clearTimeout(idle);
idle = setTimeout(() => set(false), 600);
}, { passive: true });
bar.addEventListener('pointerenter', () => set(false));
bar.addEventListener('focusin', () => set(false));
})();
```How this works
The JS got smaller, not bigger. The CSS version toggles .is-compact and the stylesheet carries three extra rules. Here the handler writes one attribute:
bar.dataset.compact = 'true'; // that is the entire state layerBoth states live in one class list. Resting geometry and collapsed geometry are typed next to each other, so the diff between them is visible without leaving the markup:
class="group flex gap-1.5 px-3.5 py-2.5 rounded-full
transition-[padding,gap] duration-[450ms] ease-nb01
data-[compact=true]:gap-0.5 data-[compact=true]:p-2"The labels are reached with group-data-*. A label cannot ask about its grandparent on its own, so the bar is marked group and each label collapses itself: group-data-[compact=true]:max-w-0 group-data-[compact=true]:opacity-0. The text stays in the DOM, so screen readers still announce it while the bar is icon-only.
Hover and focus force the expanded state. The handler clears the attribute on pointerenter and focusin, which is the accessible half of the interaction: a keyboard user who tabs into a collapsed bar gets it back at full width.
Glass needs something to refract. The root ships a conic mesh on bg-[length:200%_200%] drifting under animate-nb01-bg, and the scroll container is a real overflow region, so backdrop-blur always has moving colour and live text passing beneath it.
Make it yours
- Change the shrink trigger by editing
THRESHOLDin the script; nothing in the markup depends on the number. - Recolour the active pill by editing
--color-nb01-accentin@theme— the fill and its glow shadow both read that token. - Retime the morph by editing the two
duration-[450ms]values in place, or point--ease-nb01at a calmer curve for a less springy bar. - Collapse further by adding
data-[compact=true]:px-2to the tabs, or keep labels always visible by deleting the twogroup-data-*utilities on the label span. - Move the bar to the top with
top-[22px] bottom-auto— the frost recipe is position-independent.
Gotchas — read before shipping
- Arbitrary values cannot contain spaces:
bg-[color-mix(in_oklab,white_16%,transparent)]needs the underscore escape, and a literal space silently ends the class. data-[compact=true]:matches the attribute value, so the handler must write'false'rather than removing the attribute if you also style the resting state through it.group-data-*requires the ancestor to carrygroup. Forgetting it is the single most common port bug and fails silently.backdrop-blurneeds a translucent fill and something scrolling underneath — over an opaque parent it renders nothing at all.- Keep
-webkit-backdrop-filterin mind for older Safari; Tailwind'sbackdrop-blur-*emits the prefixed pair, but hand-written arbitrary[backdrop-filter:...]does not. - Animating
max-widthis a layout property. It is cheap here because the labels are tiny, but do not scale this pattern to large panels — animate transform instead.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Tailwind v4's generated sheet sets the floor (cascade layers, @property, color-mix()). The scroll collapse is progressive enhancement: with no JS the bar is a static, fully usable full-width glass tab bar.