Floating Navbar
A header that hides on scroll-down and reappears on scroll-up, giving long reading surfaces the vertical space back without losing the nav.
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/floating-navbar.jsonPrompt
The prompt behind this component — paste it into your AI assistant to recreate or adapt it.
Build a React + TypeScript + Tailwind "FloatingNavbar" component (no animation
library — one CSS transform plus a transition).
Contract
- Export a forwardRef<HTMLElement, FloatingNavbarProps> rendering a <header>.
- children: ReactNode — the consumer's own nav content (logo, links, actions).
This component is a shell only; it renders nothing of its own besides children.
- target?: RefObject<HTMLElement | null> — scrollable container to track;
omit to track the window.
- threshold?: number (default 80) — top-of-page exemption band in px. While
the tracked scroll position is within this band, the bar always stays
visible, regardless of direction.
- Standard className passthrough, merged via cn(), plus the rest of
React.HTMLAttributes<HTMLElement> spread onto the root.
Behavior
- Track direction with a ref-held "last scrollTop", not React state — the
scroll handler runs at high frequency and must not re-render on every tick.
- Attach one passive scroll listener, throttled to one direction check per
animation frame via requestAnimationFrame (guard re-entrancy with a raf id,
cancel it on unmount).
- On every tick: read the current scrollTop (container or window.scrollY). If
it's <= threshold, force the bar visible and reset the reference point —
no direction math inside the exemption band. Otherwise compute
delta = current - lastScrollTop. If |delta| < 8px, ignore it entirely (do
not update the reference point either) — this is the jitter guard against
trackpad/momentum micro-scrolls. Otherwise set hidden = delta > 0 (scrolling
down) and update the reference point to the current value.
- Listen on both the container (if a target is given) and window — a target
element may not be scrollable yet at mount time, so both stay wired for the
component's lifetime, same lesson as this library's Scroll Progress /
Scroll to Top.
- Visibility is a single low-frequency boolean state, flipped only inside the
rAF callback, never synchronously inside the effect body.
- Focus safety: a keyboard user tabbing into the nav must never have it slide
out from under their focus. Add a `focus-within:translate-y-0` class that
always wins over the hidden transform, so any focused descendant forces the
bar back on screen regardless of scroll state.
- Clean up: remove every scroll listener and cancel any pending
requestAnimationFrame on unmount or when target/threshold change.
Rendering & styling
- Semantic tokens only: bg-background/80 + backdrop-blur for the glass
surface, border-b for the seam, no hardcoded colors.
- Root is fixed inset-x-0 top-0 z-40 by default; consumers embedding it inside
a scroll-tracked container (rather than the window) override the position
utility via className (e.g. "absolute inset-x-0 top-0") so it stays pinned
to that container instead of escaping to the viewport — cn()'s class-merge
lets a later className win over the default position class.
- Hide/show is transition-transform duration-300 plus -translate-y-full when
hidden; motion-reduce:transition-none removes the animation without
removing the functionality (it still snaps hidden/visible).
- No internal padding, height, or layout opinions — those live entirely in
the consumer's children, this component only owns positioning + the
hide/reveal transform + the glass surface.
Customization levers
- Top exemption band: threshold controls how much top-of-page scroll is
exempt from hiding — raise it for pages with a tall hero, drop it to 0 for
an immediately reactive bar.
- Jitter tolerance: the 8px delta guard is the debounce threshold — tune it
up for touch devices with jumpier momentum scroll, down for a snappier feel.
- Reveal style: swap the full -translate-y-full hide for a "peek" mode (e.g.
translate-y-[-70%] so a sliver stays visible) by changing that one class.
- Surface: bg-background/80 + backdrop-blur can drop to a flat bg-background
for a non-glass look, or pick up a shadow on the visible state.
- Pairs with a use-scroll-direction hook when other elements on the page need
the same up/down signal — this component keeps its own inline copy of the
direction logic since it doesn't need to share it.Concepts
- Top-of-page exemption band —
thresholdcarves out a zone near the top where the bar never hides, so the very first scroll tick (often noisy on mobile) can't flicker it away before the reader has even left the hero. - Ref-based direction tracking — the "last scrollTop" lives in a ref, not state, so the high-frequency scroll math costs zero re-renders; only the low-frequency hidden/visible boolean ever triggers one.
- Direction jitter guard — sub-8px deltas are ignored and don't even reset the reference point, so trackpad momentum and micro-adjustments can't flip the bar back and forth on their own.
- Focus-safe hiding —
focus-within:translate-y-0overrides the hidden transform unconditionally, so a keyboard user tabbing into the nav can never have it slide away mid-interaction. - Shell, not a layout — the component owns positioning and the hide/reveal transform only; height, padding, and every visual choice inside the bar belong entirely to the children it wraps.
- Position override via class-merge — the default
fixedpositioning is meant to be swapped forabsolutewhen the bar needs to stay pinned inside a scroll-tracked container instead of the viewport (see the sensitive demo variant), which is exactly whatcn()'s merge behavior is for.
Link Preview
An inline link that reveals a hoverable preview card — thumbnail, title, description and domain — with edge-aware flipping and touch-safe degradation.
Command Palette
A ⌘K-style command palette — portal dialog, fuzzy subsequence search, grouped commands, recent items and full keyboard navigation.