Skip to content

Navigation Bar

Edit on GitHub
@volvo-cars/css v2.3.0

The navigation bar is a full-width 3-column grid for top-level site navigation. The center column stays optically centered regardless of how much content sits on either side.

The bar is sticky by default and uses --v-space-pageoffset for inline padding, keeping content aligned with the page layout.

Use a nav element with an aria-label as the root.

volvocars.com
import { IconButton, Wordmark } from '@volvo-cars/react-icons';
export function NavigationBar() {
return (
<nav className="navigation-bar" aria-label="Main navigation">
<div slot="start">
<button type="button" className="tap-area p-8 -ml-8 lg:hidden">
Menu
</button>
<a href="#top" className="tap-area p-8 -ml-8 hidden lg:block">
Our Cars
</a>
<a href="#top" className="tap-area p-8 hidden lg:block">
Shop
</a>
</div>
<a slot="center" href="#top" className="py-16 px-8">
<Wordmark />
</a>
<div slot="end" className="gap-24">
<IconButton
icon="profile"
variant="clear"
size="large"
bleed
aria-label="Account"
className="hidden md:block"
/>
<IconButton
icon="search"
variant="clear"
size="large"
bleed
aria-label="Search"
/>
</div>
</nav>
);
}

This component uses slots to give content built-in styling.

  • slot="start" – Left-aligned items (links, menu button)
  • slot="center" – Centered item (typically the logo)
  • slot="end" – Right-aligned items (sign-in, search)
volvocars.com
<nav
class="navigation-bar"
aria-label="Main navigation"
style="border: 1px solid grey"
>
<div slot="start" class="bg-feedback-gray" style="border: 1px solid grey">
<code>start</code>
</div>
<div slot="center" class="bg-feedback-gray" style="border: 1px solid grey">
<code>center</code>
</div>
<div slot="end" class="bg-feedback-gray" style="border: 1px solid grey">
<code>end</code>
</div>
</nav>

The bar height is 48px on small screens and 64px from the md breakpoint. This value is exposed as the --v-size-navigation-bar-height custom property so sub-navigations and content can position themselves relative to it, e.g. scroll-margin-top: var(--v-size-navigation-bar-height).

The navigation bar is position: sticky by default. Override it with the --navigation-bar-position custom property — set it directly on the element as an inline style, or on :root for remote control from another component.

When two navigation bars are present, set --navigation-bar-position: static on the main bar so it scrolls away while the sub-bar stays sticky.

volvocars.com
import { IconButton, Wordmark } from '@volvo-cars/react-icons';
import type { CSSProperties } from 'react';
import { useState } from 'react';
export function SubNavigation() {
const [open, setOpen] = useState(false);
return (
<div style={{ maxHeight: 320, overflow: 'auto' }}>
<nav
className="navigation-bar"
aria-label="Main navigation"
style={
open
? ({ '--navigation-bar-position': 'static' } as CSSProperties)
: undefined
}
>
<div slot="start">
<button
type="button"
aria-expanded={open}
onClick={() => setOpen((v) => !v)}
className="tap-area p-8 -ml-8"
>
Our Cars
</button>
</div>
<a slot="center" href="#top" className="py-16 px-8">
<Wordmark />
</a>
<div slot="end">
<IconButton
icon="search"
variant="clear"
size="large"
bleed
aria-label="Search"
/>
</div>
</nav>
{open && (
<nav className="navigation-bar bg-secondary" aria-label="Our Cars">
<div slot="start">
<a href="#top" className="tap-area p-8 -ml-8">
EX30
</a>
<a href="#top" className="tap-area p-8">
EX90
</a>
</div>
<div slot="end">
<IconButton
icon="x"
variant="clear"
bleed
aria-label="Close sub-navigation"
onClick={() => setOpen(false)}
/>
</div>
</nav>
)}
<div style={{ height: 600 }} className="bg-secondary pt-24 pagelayout">
<p className="text-secondary">
{open
? 'Scroll to see the sub-navigation stick while the main bar scrolls away.'
: 'Click Our Cars to open the sub-navigation.'}
</p>
</div>
</div>
);
}
  • Use a nav element as the root with a descriptive aria-label (e.g. “Main navigation”, “Our Cars”)
  • When multiple nav elements are present, each must have a unique aria-label
  • Use aria-expanded on buttons that toggle sub-navigations
ClassDescription
navigation-barSticky 3-column navigation grid. 48px height on mobile, 64px from md breakpoint.
Custom propertyDescriptionDefault
--navigation-bar-positionControls position. Set to static to unstick.sticky
--v-size-navigation-bar-heightThe bar’s height. 48px on mobile, 64px from md.