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 token | Effect |
|---|---|
| (empty) | No hide, no class. Other features (is-current tracking, indicators, section classes) still work. |
hide | Translate 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. |
change | Add is-scrolled to the nav once scrollY > 100. Useful for shrinking padding or changing background. |
change-150 | Same, threshold 150px (any pixel value). |
hide change | Both. Tokens are space-separated and order-independent. |
none | Don'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.
| Attribute | Default | Notes |
|---|---|---|
aa-intensity | 1 | Multiplier 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-ease | osmo | Any GSAP ease — named (incl. elastic / bounce) or raw (power3.inOut). |
Frosted bar (backdrop-filter)
Section titled “Frosted bar (backdrop-filter)”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.
Scroll-spy: is-current link tracking
Section titled “Scroll-spy: is-current link tracking”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.
Indicators (Flip)
Section titled “Indicators (Flip)”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:
| Attribute | Behaviour |
|---|---|
aa-nav-current-indicator | Morphs onto whichever [aa-scroll-target] has is-current. Updates via MutationObserver and on resize. |
aa-nav-hover-indicator | Morphs 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.
Section classes
Section titled “Section classes”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%.
aa-scroll-target (click-to-scroll)
Section titled “aa-scroll-target (click-to-scroll)”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.
| Attribute | Default | Notes |
|---|---|---|
aa-duration | 1.2 | Lenis scroll duration in seconds. Ignored on the native fallback (browser controls duration). |
aa-scroll-offset | 0 | Pixel 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). |
Body scroll-state
Section titled “Body scroll-state”Two attributes the lib writes on <body> that any CSS rule can hook into. Always-on; opt out with init({ scrollState: false }).
| Body attribute | Values | When |
|---|---|---|
aa-scroll-direction | up / down | Flips when scroll movement exceeds 5px in the new direction. |
aa-scroll-started | true / false | Flips 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); }Required GSAP plugins
Section titled “Required GSAP plugins”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.