Skip to content

Components / Sections

Sticky Nav Bar

A sticky navigation bar that condenses over the first 120 px of scroll: the row tightens and a hairline appears, bound to the document's scroll timeline, with zero JavaScript. mode="hide" also slides it away on the way down and back on the way up (a measured script, in every engine), mode="static" keeps it still. An optional theme switch at its end and an optional announcement banner above it, in the same sticky header.

Live demo

A sample announcement, in the bar's header.

scroll inside

Line 1: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 2: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 3: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 4: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 5: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 6: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 7: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 8: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 9: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 10: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 11: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 12: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 13: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 14: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 15: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 16: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 17: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 18: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 19: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 20: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 21: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 22: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 23: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Line 24: scroll down and the bar slides away, scroll up and it comes back. The mode row below switches it.

Live, from the library itself, at full size: scroll, hover, navigate away and back. The demo is never gated.

Variants · each row writes the attribute the prop writes; the section's own CSS answers

tone
density
mode

Measured

JavaScript of its own

0 B

With hide: 613 B raw of its own on the runtime, measured on its own fixture. The default above ships none of it.

CSS 15.1 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.

Browser support

Limited availability

Chrome 120 · not Firefox · Safari 26; needs scroll-timeline, property, mask-image. Elsewhere: a sticky bar with the hairline that does not condense (Firefox); hide works everywhere, since it is a script; on a phone the scrolling links get a static end fade.

Install Pro

The demo above is never gated; the source is. With a key in components.json (see account), the registry serves it like any other item:

terminal
npx shadcn@latest add @moonarc-pro/nav-bar
src/pages/index.astro
---
import NavBar from '@/components/moonarc/pro/NavBar.astro';
---
<NavBar
  brand={{ href: '/', label: 'Moonarc' }}
  links={[{ href: '/components/', label: 'Components', current: true }, { href: '/docs/', label: 'Docs' }]}
  action={{ href: '/setup/', label: 'Get started' }}
  mode="hide"
  themeToggle
/>

<style is:global>
  /* anchors and focused fields land under the bar, not behind it */
  html { scroll-padding-top: 5rem; }
</style>

Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: theme-toggle.

Props

PropTypeDefaultDescription
brand{ href, label }noneLogo link.
links{ href, label, current? }[]noneNavigation links; current marks the active page.
labelstring'Primary'Accessible name of the link nav. It must be unique per page, so change it when the bar is not the page's own (a preview, a second bar).
action{ href, label }noneCall to action at the end of the bar (the left in a right-to-left page).
mode'condense' | 'hide' | 'static''condense'condense: the row tightens and the hairline appears over the first 120 px, CSS only. hide: the same, and past its own height the bar slides away while the page scrolls down and comes back when it scrolls up, at the top, and when focus or an open menu is inside it; an internal script of its own, measured on its own fixture, bound to the window or to the nearest scrolling ancestor. static: a sticky bar with its hairline, nothing bound to the scroll. Set scroll-padding-top on the page to the bar's height so anchors land under it.
themeTogglebooleanfalseCore ThemeToggle at the end of the actions, with its own name ("Theme: system"). It needs ThemeScript in the head, and without JavaScript it is hidden.
tone'surface' | 'muted' | 'ink' | 'brand'unset (translucent card)The ground: the page surface, a muted band, an always-dark ink panel, or a deep brand panel. Each paints --background, --ma-tone-muted (over --muted, with a --border hairline), --ma-tone-ink or --ma-tone-brand (over --primary) with the matching text. With theme.css, a painted tone also re-declares --card, --border, --input and --ring inside the section, so cards and fields follow the ground. Decoration is strongest on brand.
density'compact' | 'cozy' | 'roomy''cozy'Bar padding ×0.8 / ×1 / ×1.25 through --ma-density.

Theming and overrides

The section reads the theme contract (shadcn's variable names with the base tokens as fallback), so a pasted theme or one --ma-brand recolours it. Its class names are the override surface and stay stable; a named slot replaces the matching prop with your own markup.

Classes

.ma-banner.ma-navbar.ma-navbar__action.ma-navbar__actions.ma-navbar__banner.ma-navbar__brand.ma-navbar__inner.ma-navbar__links.ma-navbar__theme

Slots

slot="banner"slot="headline"slot="actions"

Reduced motion

Always condensed, hairline always on; hide never hides.

With ClientRouter

CSS-only by default; the new page's bar starts at its own scroll position. The hide script binds through the shared runtime: one scroll listener per bar, released before each swap and bound again after it.

Why it is built this way

Replaces: Navbar (Tailwind Plus) · FloatingNav (Aceternity). See the migration table.

A hairline along the top of the viewport that fills as the page is read.

Chrome 115 · not Firefox · Safari 26scroll

Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown