Skip to content

nav

aa-nav is a small cluster of behaviours bundled around a single nav element. Pick the ones you need by composing space-separated tokens on the aa-nav attribute and adding optional sub-attributes to children of the nav.

aa-nav tokenEffect
(empty)No hide, no class. Other features (is-current tracking, indicators, section classes) still work.
hideTranslate the nav up off-screen when the user scrolls down (only past the global ~50px scroll-started threshold), slide it back in when they scroll up.
changeAdd is-scrolled to the nav once scrollY > 100. Useful for shrinking padding or changing background.
change-150Same, threshold 150px (any pixel value).
hide changeBoth. Tokens are space-separated and order-independent.
noneDon't init at this breakpoint. Combine with | or -sm / -md / -lg / -xl suffixes.

The hide is GSAP-driven. The library watches the body's scroll-state attributes and, on the down→up / up→down flip, tweens the nav's yPercent off-screen (-150% × aa-intensity) and back, using your aa-duration / aa-ease. overwrite: 'auto' means a mid-hide direction reversal picks up smoothly from the current position rather than snapping.

It runs through GSAP — not a CSS keyframe or transition — on purpose. A nav very commonly also carries an aa-animate entrance (e.g. slide-down), and GSAP's entrance tween owns the element's transform (it even zeroes the individual translate / rotate / scale CSS properties so they can't fight it). Driving the hide through GSAP too means one system owns transform: the hide composes with the entrance instead of conflicting with it, and an author's own transition: … on the nav (for the is-scrolled state) can't clobber it. You're free to add any transition you like on the nav for the change state — it's independent of the hide.

The hide threshold is shared, not per-nav: the body picks up aa-scroll-started="true" once window.scrollY > 50 (set by the body scroll-state tracker) and the hide rule is gated on that. This keeps the nav put while you're at the top of the page so a small mouse-wheel nudge downward doesn't immediately hide it. The change token, by contrast, is per-nav and configurable via change-<px> because it just toggles a class on its own element.

AttributeDefaultNotes
aa-intensity1Multiplier on the internal -150% hide-translate baseline. 1 (default) slides the nav to -150% — far enough to clear typical drop-shadows. 0.67 brings it to -100% (just clears the nav itself); 2 doubles to -300%.
aa-duration(init duration)GSAP tween duration in seconds.
aa-easeosmoAny GSAP ease — named (incl. elastic / bounce) or raw (power3.inOut).

backdrop-filter is neutralised when an ancestor has transform, filter, or will-change — but never by an element's own transform. So put backdrop-filter on the same element that carries aa-nav / aa-animate, not a deeper descendant, and the blur survives every state (entrance, hide, mid-slide):

<nav class="nav" aa-nav="hide change" aa-animate="slide-down" aa-trigger="load">
<div class="nav-content"><!-- logo / links / button --></div>
</nav>
.nav.is-scrolled { background: rgba(255,255,255,.6); backdrop-filter: blur(10px); }

A blur on a descendant works at rest too, but can drop briefly during the slide-in — co-locate to avoid that.

Any [aa-scroll-target] link inside the nav whose target selector resolves to a section automatically picks up an is-current class while that section is the one closest to the viewport's middle. ScrollTrigger watches each section between 0% 50% and 100% 50%. Style off [aa-scroll-target].is-current for active-state styling.

This is independent of the click-to-scroll behaviour described in the scroll-target section below — both share the same attribute.

Add an empty element inside the nav with one of these attributes and the lib will Flip-morph it to track the active or hovered link:

AttributeBehaviour
aa-nav-current-indicatorMorphs onto whichever [aa-scroll-target] has is-current. Updates via MutationObserver and on resize.
aa-nav-hover-indicatorMorphs onto the hovered link, returns to the current link on mouseleave of the nav. Disabled on touch devices.

Both read aa-duration (default 0.4s) and aa-ease (default power2.out) from the indicator element itself. Indicator easing is GSAP-driven (it animates via Flip.fit), so the full GSAP ease vocabulary is available — including elastic.out and bounce.out.

The lib starts indicators at opacity: 0 until the first Flip lands; set the initial style yourself if you want a different fade-in.

Add aa-nav-section="my-class" to a section anywhere on the page. While that section is in view, my-class is on the nav element. Useful for swapping the nav's colour scheme per section (light header over a hero, dark over a content panel).

<section aa-nav-section="is-light" aa-scroll-start="top 0%" aa-scroll-end="bottom 0%"></section>
<section aa-nav-section="is-dark" ></section>

aa-scroll-start / aa-scroll-end on the section override the default ScrollTrigger window of top 0% / bottom 0%.

A small global utility (not nav-specific): clicking any [aa-scroll-target="#selector"] element smooth-scrolls to that selector. If Lenis is loaded (the default), the scroll runs through lenis.scrollTo with a quartic ease and your aa-duration. If Lenis isn't active, the lib falls back to native window.scrollTo({ behavior: 'smooth' }) — same aa-scroll-offset offset, no library configuration required.

AttributeDefaultNotes
aa-duration1.2Lenis scroll duration in seconds. Ignored on the native fallback (browser controls duration).
aa-scroll-offset0Pixel offset applied to the target's top — negative pulls the target above the viewport edge (useful when a fixed header would otherwise overlap the anchor).

Two attributes the lib writes on <body> that any CSS rule can hook into. Always-on; opt out with init({ scrollState: false }).

Body attributeValuesWhen
aa-scroll-directionup / downFlips when scroll movement exceeds 5px in the new direction.
aa-scroll-startedtrue / falseFlips to true once scrollY > 50. Used by the nav-hide CSS rule to suppress the hide animation while at the top of the page.
body[aa-scroll-direction="down"] .my-floating-button { opacity: 0; pointer-events: none; }
body[aa-scroll-started="true"] .hero { background: var(--page-bg-pinned); }

ScrollTrigger for is-current tracking and section classes, Flip for the indicators. The hide / change / scroll-target / scroll-state behaviours all run without any GSAP plugin (CSS variables + plain scroll listeners). If Flip is missing, indicators silently no-op.

Live demo — sticky pill nav with hover + current indicators and section colour swap

Section titled “Live demo — sticky pill nav with hover + current indicators and section colour swap”

A persistent (non-hiding) sticky nav. The current-link indicator (lime) sits behind the active link permanently; the hover indicator (accent-colour border, no fill) slides over whichever link the cursor is on and returns to the current link on mouseleave. While the third section is in view, aa-nav-section="is-accent" flips the nav into its accent colour scheme.

aa-nav (no token — no hide, no is-scrolled). Indicators carry aa-duration="0.45" + aa-ease="power3.out" (current) and aa-duration="0.35" + aa-ease="power2.out" (hover). Both pills get border-radius: 999px via CSS so Flip morphs between rounded shapes.

overview

The current-link indicator (lime pill) follows whichever section's middle is in the viewport. Click any link to scroll to that section.

approach

Hover any link and the accent-bordered pill slides over it via Flip. Move the cursor away — it returns to the current pill.

work

This section adds is-accent to the nav. The whole bar inverts; the current pill flips to white so it stays readable on the dark background.

contact

Scrolling back up cleans up the section class in reverse order.

Live demo — same nav, hides on scroll-down

Section titled “Live demo — same nav, hides on scroll-down”

Identical pill styling, only the current indicator (no hover indicator) and an aa-nav="hide change" modifier so it slides up off-screen when you scroll down and returns on scroll-up. Once you're past 100px the is-scrolled class kicks in too.

aa-nav="hide change" — the default aa-intensity="1" already slides the nav to -150%, which clears any drop-shadow. The current indicator carries aa-duration="0.45" + aa-ease="power3.out".

overview

Scroll down — the nav slides up out of view. Scroll back up and it returns.

approach

Once you've scrolled more than 100px, the nav picks up is-scrolled — the demo uses that to add a subtle drop-shadow.

work

The current pill tracks whichever section is in view, morphed via Flip.

contact

The body picks up aa-scroll-direction and aa-scroll-started attributes — the GSAP-driven hide just tweens the nav off-screen when they flip to scrolling down.