Floating Pill Nav — Free HTML CSS JS Navbar Snippet

Floating Pill Nav · Navigation · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Frosted glass pill
backdrop-filter blur over a translucent rounded bar.
Shrinks on scroll
A single .shrink class morphs width, height, and shadow.
Sliding indicator
A pill follows the hovered and active link via transform.
Passive rAF scroll
Throttled scroll handling keeps the main thread free.
Returns to active
The highlight snaps back when the mouse leaves the list.
Resize-aware
Indicator re-measures on viewport changes.
Responsive collapse
Center links hide below 560px for mobile.
CSS-driven morph
JS only toggles a class; transitions do the animation.

About this UI Snippet

Floating Pill Nav — Shrinking Glass Bar with Sliding Indicator

Screenshot of the Floating Pill Nav snippet rendered live

The floating pill navbar is the navigation style popularized by modern SaaS and product sites: instead of a full-width bar stuck to the top edge, the menu is a rounded "pill" that floats a few pixels below the top, centered, with a frosted-glass background — and it gently condenses as you scroll down. This snippet implements the complete pattern in plain HTML, CSS, and vanilla JavaScript, including a sliding indicator that tracks the active and hovered links.

The floating glass container

The nav is position: fixed and centered with the classic left: 50%; transform: translateX(-50%) trick, then capped at min(720px, calc(100% - 32px)) so it never touches the screen edges. The frosted look comes from a semi-transparent rgba background plus backdrop-filter: blur(10px), which samples and blurs whatever scrolls behind it. A hairline 1px border and a soft drop shadow lift it off the page so it reads as a floating object rather than a docked bar.

Condensing on scroll

A scroll listener — registered as { passive: true } and throttled with requestAnimationFrame so it never blocks the main thread — toggles a .shrink class once window.scrollY passes 40px. That single class narrows the pill from 720px to 560px, raises it closer to the top, tightens the padding, and deepens both the background opacity and the shadow. Every one of those changes is driven by CSS transitions on width, top, padding, background, and box-shadow, so the JavaScript only flips a class while CSS animates the morph smoothly with a cubic-bezier ease.

The sliding active indicator

A .fp-pill element is created in JavaScript and appended behind the links. The movePill function reads the target link's offsetLeft and offsetWidth and applies them as a translateX and width, so the highlight glides to sit exactly under any link. It follows the mouse on mouseenter, snaps to the clicked link as the new active item, and returns to the active link when the cursor leaves the list via mouseleave. Because it animates transform and width rather than left, the motion is GPU-accelerated and stays at 60fps.

Staying correct on resize

Link positions change when the viewport resizes, so a resize listener re-measures and repositions the indicator under the current active link. On first paint the position is set inside a requestAnimationFrame callback to guarantee layout has been computed before measuring offsetLeft — measuring too early would place the pill at zero.

Responsive behavior

Below 560px the center link list is hidden with a media query, leaving the brand and the call-to-action button — the typical mobile collapse point where you would swap in a hamburger nav or a bottom nav. The CTA keeps a subtle hover lift so the primary action stays obvious at any size.

Customizing it

Change the two width values to control how dramatically the bar condenses, adjust the scrollY > 40 threshold to shrink sooner or later, and recolor the indicator's rgba background to match your accent. Because the shrink is just a class toggle, you can add more changes — hide the brand text, swap the logo, or fade in a search icon — purely by extending the .shrink rules in CSS.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to trace movePill and the scroll handler by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the first call to movePill is wrapped in requestAnimationFrame instead of running immediately at script load, and why the scroll listener sets a ticking flag and defers its work into another requestAnimationFrame rather than reacting on every scroll event directly. The same assistant can help optimize it — ask whether the resize listener should be debounced for very rapid window resizing, or whether offsetLeft/offsetWidth reads could be cached between mouse events on a link that hasn't moved. It's also useful for extending the nav: have it add a dropdown submenu that also uses a sliding-pill highlight, sync the active link to scroll position with an IntersectionObserver instead of only clicks, or make the shrink threshold responsive to viewport height. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a floating, centered "pill" navigation bar in plain HTML, CSS, and JavaScript that shrinks on scroll and has a sliding highlight indicator — no libraries.

Requirements:
- A fixed-position nav bar horizontally centered near the top of the viewport using left: 50% with a translateX(-50%) transform, capped at a maximum width with a small margin from the viewport edges on narrow screens. Give it a frosted-glass look with a semi-transparent background and backdrop-filter blur, a subtle border, and a soft drop shadow.
- A brand link, a horizontal list of nav links, and a call-to-action button/link inside the bar.
- A single extra element (not one of the real links) that acts as a sliding highlight/indicator positioned absolutely behind the links list. Write a function that, given a target link element, reads its offsetLeft and offsetWidth and applies them to the indicator as a translateX and a width, so the indicator visually sits exactly under that link — animate this with CSS transitions on transform and width, not left/right, so it stays GPU-accelerated.
- The indicator must follow whichever link is hovered (mouseenter), snap to a link when it's clicked (marking that link as active and un-marking the others, with e.preventDefault so the anchor doesn't navigate), and return to the currently-active link when the mouse leaves the whole link list.
- Position the indicator correctly on initial page load by deferring the first positioning call to a requestAnimationFrame callback, since element offsets are unreliable before the browser has completed its first layout pass.
- Add a resize listener that repositions the indicator under the current active link whenever the viewport size changes.
- Add a scroll listener that toggles a single "shrink" class on the nav bar once the page has scrolled past a small threshold (e.g. 40px), and register that listener as passive and throttle its work through requestAnimationFrame using a ticking flag, so it never runs its logic more than once per animation frame. All the resulting visual change (narrower width, higher position, tighter padding, denser background) must be expressed purely as CSS transitions triggered by that one class, not JavaScript-driven value interpolation.
- Below a narrow viewport width, hide the center link list with a media query, leaving only the brand and the call-to-action visible.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA frosted pill navbar floats centered near the top of the page.
  2. 2
    Hover the linksA soft indicator slides under whichever link you point at.
  3. 3
    Click a linkThe indicator snaps to it and it becomes the active item.
  4. 4
    Scroll downThe whole pill narrows, rises, and deepens its glass background.
  5. 5
    Resize the windowThe indicator re-measures so it stays aligned under the active link.
  6. 6
    Tune the thresholdsAdjust the width values and scrollY trigger to taste.

Real-world uses

Common Use Cases

SaaS landing pages
Float above a gradient mesh hero section.
Product marketing sites
Pair with a sticky header alternative pattern.
Portfolios
Top a portfolio hero with a minimal floating menu.
Docs sites
Combine with a scroll spy nav for section tracking.
Mobile fallback
Swap to a hamburger nav under 560px.
Animated nav demos
A reference for a transform-based sliding indicator.

Got questions?

Frequently Asked Questions

JavaScript only toggles a .shrink class when scrollY passes 40px; all the actual change — narrower width, higher top, tighter padding, denser background — lives in CSS transitions with a cubic-bezier ease. The scroll listener is passive and wrapped in requestAnimationFrame, so measuring and the class flip never block scrolling.

movePill reads the target link's offsetLeft and offsetWidth and applies them as a translateX and width on an absolutely-positioned pill behind the links. It animates transform and width (GPU-friendly) rather than the left property, so it glides smoothly under the hovered link and snaps to the active one on click.

offsetLeft and offsetWidth return zero until the browser has computed layout. Deferring the first movePill call to a requestAnimationFrame guarantees the links have been laid out, so the pill appears correctly under the initial active link instead of at the far left.

Yes. A resize listener calls movePill on the current active link whenever the viewport changes, recalculating its offset. Below 560px a media query hides the center links entirely, which is the natural breakpoint to swap in a hamburger or bottom-nav menu.

Render the links from an array and keep an activeIndex in state. Use a ref to the active link element and recompute the indicator's left/width in a layout effect (useLayoutEffect in React, onMounted plus a watcher in Vue, ngAfterViewInit in Angular). Attach the scroll and resize listeners in the same effect and return a cleanup that removes them. In Tailwind, build the pill with backdrop-blur, bg-white/10, and rounded-full utilities.