Floating Nav
A sleek, auto-hiding navigation bar that floats above your content and uses backdrop-filter.
Scroll up or down on this page to see the nav bar appear and disappear!
(Make sure your window is scrollable)
Installation
1. Add the CSS to your global stylesheet:
css
/* Floating Nav */
.floating-nav { position: fixed; top: 1.5rem; left: 50%; transform: translateX(-50%); z-index: 50; transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease; padding: 0.5rem; border-radius: 9999px; background: var(--glass-bg); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid var(--glass-border); box-shadow: 0 10px 30px -10px rgba(0,0,0,0.1); width: fit-content; max-width: 90vw; }
.floating-nav-visible { transform: translate(-50%, 0); opacity: 1; pointer-events: auto; }
.floating-nav-hidden { transform: translate(-50%, -150%); opacity: 0; pointer-events: none; }
.floating-nav-list { display: flex; gap: 0.25rem; list-style: none; margin: 0; padding: 0; align-items: center; }
.floating-nav-item { margin: 0; }
.floating-nav-link { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; border-radius: 9999px; color: var(--text-primary); text-decoration: none; font-size: 0.875rem; font-weight: 600; transition: all 0.2s ease; position: relative; }
.floating-nav-link:hover { background: var(--bg-secondary); color: var(--accent); }
.floating-nav-icon { display: flex; align-items: center; justify-content: center; width: 1.25rem; height: 1.25rem; }2. Copy the React component into your project:
tsx
"use client";
import { ReactNode, useEffect, useState, useRef } from "react";
import { isSafeHref } from "./is-safe-href";
export interface NavItem {
name: string;
link: string;
icon?: ReactNode;
}
export interface FloatingNavProps {
navItems: NavItem[];
className?: string;
}
/**
* A zero-dependency, ultra-lightweight floating navigation component.
* Hides on scroll down, shows on scroll up.
*/
export const FloatingNav = ({ navItems, className = "" }: FloatingNavProps) => {
const [isVisible, setIsVisible] = useState(true);
const lastScrollY = useRef(0);
useEffect(() => {
if (typeof window === "undefined") return;
const handleScroll = () => {
const currentScrollY = window.scrollY;
// If scroll is near top, always show
if (currentScrollY < 50) {
setIsVisible(true);
} else {
// Scrolling UP
if (currentScrollY < lastScrollY.current) {
setIsVisible(true);
}
// Scrolling DOWN
else if (currentScrollY > lastScrollY.current) {
setIsVisible(false);
}
}
lastScrollY.current = currentScrollY;
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<nav
className={`floating-nav ${isVisible ? "floating-nav-visible" : "floating-nav-hidden"} ${className}`}
>
<ul className="floating-nav-list">
{navItems.map((item, idx) => (
<li key={idx} className="floating-nav-item">
<a
href={isSafeHref(item.link) ? item.link : "#"}
className="floating-nav-link"
>
{item.icon && (
<span className="floating-nav-icon">{item.icon}</span>
)}
<span className="floating-nav-text">{item.name}</span>
</a>
</li>
))}
</ul>
</nav>
);
};
Usage
tsx
import { FloatingNav } from '@/components/ui/floating-nav';
import { Home, Info, Mail } from 'lucide-react';
export default function MyComponent() {
const navItems = [
{ label: 'Home', href: '/', icon: <Home size={16} /> },
{ label: 'About', href: '/about', icon: <Info size={16} /> },
{ label: 'Contact', href: '/contact', icon: <Mail size={16} /> }
];
return (
<>
<FloatingNav items={navItems} />
<main>
{/* Your content here */}
</main>
</>
);
}