Skip to main content
Light Dark System

Header

<cw-header> | CwHeader
Since 1.3 experimental

Page header region composing branding, primary navigation, icon-triggered action panels, and secondary navigation into one piece of chrome.

<cw-header> composes everything a site’s persistent page chrome typically needs, via named slots: logo, primary-navigation, five icon-triggered action slots (utility, search, notification, user, cart), and secondary-navigation. Any slot you don’t populate renders nothing — no reserved empty space.

Below, mobile-breakpoint is set low enough that this preview renders in its desktop layout — horizontal primary navigation alongside the logo — even inside the doc site’s fairly narrow preview frame. Shrink the frame (drag its bottom-right corner) below the breakpoint to see it collapse to the mobile, hamburger layout instead. See Mobile Breakpoint below for a preview forced into that layout instead.

Home Products
Your cart is empty.
Overview Installation
<cw-header mobile-breakpoint="320">
  <cw-logo
    slot="logo"
    image="/assets/images/Cordwainer-logo-light-mode-vector.svg"
    dark-image="/assets/images/Cordwainer-logo-dark-mode-vector.svg"
    href="/"
    label="Cordwainer home"
  ></cw-logo>

  <cw-primary-navigation slot="primary-navigation" mobile-breakpoint="320">
    <cw-primary-navigation-item href="/" current>Home</cw-primary-navigation-item>
    <cw-primary-navigation-item href="/">Products</cw-primary-navigation-item>
  </cw-primary-navigation>

  <div slot="search" style="padding: 1rem;">
    <cw-input placeholder="Search..." clearable></cw-input>
  </div>

  <div slot="cart" style="padding: 1rem;">Your cart is empty.</div>

  <cw-secondary-navigation slot="secondary-navigation">
    <cw-secondary-navigation-item href="/" current>Overview</cw-secondary-navigation-item>
    <cw-secondary-navigation-item href="/">Installation</cw-secondary-navigation-item>
  </cw-secondary-navigation>
</cw-header>
import { CwHeader, CwInput, CwLogo, CwPrimaryNavigation, CwPrimaryNavigationItem, CwSecondaryNavigation, CwSecondaryNavigationItem } from '@cordwainer/cw-elements/dist/react';

const App = () => (
  <CwHeader mobileBreakpoint={320}>
    <CwLogo
      slot="logo"
      image="/assets/images/Cordwainer-logo-light-mode-vector.svg"
      darkImage="/assets/images/Cordwainer-logo-dark-mode-vector.svg"
      href="/"
      label="Cordwainer home"
    />

    <CwPrimaryNavigation slot="primary-navigation" mobileBreakpoint={320}>
      <CwPrimaryNavigationItem href="/" current>
        Home
      </CwPrimaryNavigationItem>
      <CwPrimaryNavigationItem href="/">Products</CwPrimaryNavigationItem>
    </CwPrimaryNavigation>

    <div slot="search" style={{ padding: '1rem' }}>
      <CwInput placeholder="Search..." clearable />
    </div>

    <div slot="cart" style={{ padding: '1rem' }}>
      Your cart is empty.
    </div>

    <CwSecondaryNavigation slot="secondary-navigation">
      <CwSecondaryNavigationItem href="/" current>
        Overview
      </CwSecondaryNavigationItem>
      <CwSecondaryNavigationItem href="/">Installation</CwSecondaryNavigationItem>
    </CwSecondaryNavigation>
  </CwHeader>
);

Examples

Action Panels

Each of the five action slots — utility (gear), search (magnifying glass), notification (bell), user (person), cart (basket) — gets a fixed icon trigger automatically when populated. Clicking a trigger opens a panel below the header showing that slot’s content; only one panel is open at a time, and clicking a different trigger while one is already open swaps the content directly. Panels close on Escape or on clicking outside, and the trigger receives focus back on close.

Utility settings go here.
You’re all caught up.
Signed in as jane@example.com
Your cart is empty.
<cw-header>
  <div slot="utility" style="padding: 1rem;">Utility settings go here.</div>
  <div slot="search" style="padding: 1rem;">
    <cw-input placeholder="Search..." clearable></cw-input>
  </div>
  <div slot="notification" style="padding: 1rem;">You're all caught up.</div>
  <div slot="user" style="padding: 1rem;">Signed in as jane@example.com</div>
  <div slot="cart" style="padding: 1rem;">Your cart is empty.</div>
</cw-header>
import { CwHeader, CwInput } from '@cordwainer/cw-elements/dist/react';

const App = () => (
  <CwHeader>
    <div slot="utility" style={{ padding: '1rem' }}>
      Utility settings go here.
    </div>
    <div slot="search" style={{ padding: '1rem' }}>
      <CwInput placeholder="Search..." clearable />
    </div>
    <div slot="notification" style={{ padding: '1rem' }}>
      You're all caught up.
    </div>
    <div slot="user" style={{ padding: '1rem' }}>
      Signed in as jane@example.com
    </div>
    <div slot="cart" style={{ padding: '1rem' }}>
      Your cart is empty.
    </div>
  </CwHeader>
);

Panel Width

Above mobile-breakpoint, an open panel is flush right at 50% of the header’s width by default. Override it with the --panel-width custom property. Below the breakpoint, panels are always full width.

<cw-header style="--panel-width: 320px;">
  <div slot="search" style="padding: 1rem;">
    <cw-input placeholder="Search..."></cw-input>
  </div>
</cw-header>
import { CwHeader, CwInput } from '@cordwainer/cw-elements/dist/react';

const App = () => (
  <CwHeader style={{ '--panel-width': '320px' }}>
    <div slot="search" style={{ padding: '1rem' }}>
      <CwInput placeholder="Search..." />
    </div>
  </CwHeader>
);

Mobile Breakpoint

Set mobile-breakpoint (in pixels) to control when the header switches to its stacked mobile layout — logo and primary navigation on the first row (primary navigation’s own hamburger takes over), action triggers on the second, secondary navigation on the third. Defaults to 768, matching <cw-primary-navigation>’s own default so the two switch together out of the box.

Home About
<cw-header mobile-breakpoint="900" class="header-demo-narrow">
  <cw-logo
    slot="logo"
    image="/assets/images/Cordwainer-logo-light-mode-vector.svg"
    dark-image="/assets/images/Cordwainer-logo-dark-mode-vector.svg"
    href="/"
    label="Cordwainer home"
  ></cw-logo>
  <cw-primary-navigation slot="primary-navigation">
    <cw-primary-navigation-item href="/">Home</cw-primary-navigation-item>
    <cw-primary-navigation-item href="/">About</cw-primary-navigation-item>
  </cw-primary-navigation>
  <div slot="search" style="padding: 1rem;">
    <cw-input placeholder="Search..."></cw-input>
  </div>
</cw-header>

<style>
  .header-demo-narrow {
    max-width: 400px;
  }
</style>
import { CwHeader, CwInput, CwLogo, CwPrimaryNavigation, CwPrimaryNavigationItem } from '@cordwainer/cw-elements/dist/react';

const css = `
  .header-demo-narrow {
    max-width: 400px;
  }
`;

const App = () => (
  <>
    <CwHeader className="header-demo-narrow" mobileBreakpoint={900}>
      <CwLogo
        slot="logo"
        image="/assets/images/Cordwainer-logo-light-mode-vector.svg"
        darkImage="/assets/images/Cordwainer-logo-dark-mode-vector.svg"
        href="/"
        label="Cordwainer home"
      />
      <CwPrimaryNavigation slot="primary-navigation">
        <CwPrimaryNavigationItem href="/">Home</CwPrimaryNavigationItem>
        <CwPrimaryNavigationItem href="/">About</CwPrimaryNavigationItem>
      </CwPrimaryNavigation>
      <div slot="search" style={{ padding: '1rem' }}>
        <CwInput placeholder="Search..." />
      </div>
    </CwHeader>

    <style>{css}</style>
  </>
);

Slot a <cw-primary-navigation-menu> into a <cw-primary-navigation-item>’s menu slot to give it a dropdown of sub-links — see Primary Navigation Item for details. Above mobile-breakpoint, that dropdown floats below its trigger like any other. Below it, forced here with a narrow wrapper, it instead expands inline and full width, right where the trigger sits, matching the rest of the mobile menu: tap “Products” or “Company” below to open the hamburger menu, then tap either one to expand its sub-links in place.

Home Products Shoes Boots Sandals Company About Careers
<cw-header mobile-breakpoint="900" class="header-demo-narrow">
  <cw-logo
    slot="logo"
    image="/assets/images/Cordwainer-logo-light-mode-vector.svg"
    dark-image="/assets/images/Cordwainer-logo-dark-mode-vector.svg"
    href="/"
    label="Cordwainer home"
  ></cw-logo>
  <cw-primary-navigation slot="primary-navigation">
    <cw-primary-navigation-item href="/" current>Home</cw-primary-navigation-item>
    <cw-primary-navigation-item>
      Products
      <cw-primary-navigation-menu slot="menu">
        <a href="/">Shoes</a>
        <a href="/">Boots</a>
        <a href="/">Sandals</a>
      </cw-primary-navigation-menu>
    </cw-primary-navigation-item>
    <cw-primary-navigation-item>
      Company
      <cw-primary-navigation-menu slot="menu">
        <a href="/">About</a>
        <a href="/">Careers</a>
      </cw-primary-navigation-menu>
    </cw-primary-navigation-item>
  </cw-primary-navigation>
</cw-header>

<style>
  .header-demo-narrow {
    max-width: 400px;
  }
</style>
import { CwHeader, CwLogo, CwPrimaryNavigation, CwPrimaryNavigationItem, CwPrimaryNavigationMenu } from '@cordwainer/cw-elements/dist/react';

const css = `
  .header-demo-narrow {
    max-width: 400px;
  }
`;

const App = () => (
  <>
    <CwHeader className="header-demo-narrow" mobileBreakpoint={900}>
      <CwLogo
        slot="logo"
        image="/assets/images/Cordwainer-logo-light-mode-vector.svg"
        darkImage="/assets/images/Cordwainer-logo-dark-mode-vector.svg"
        href="/"
        label="Cordwainer home"
      />
      <CwPrimaryNavigation slot="primary-navigation">
        <CwPrimaryNavigationItem href="/" current>
          Home
        </CwPrimaryNavigationItem>
        <CwPrimaryNavigationItem>
          Products
          <CwPrimaryNavigationMenu slot="menu">
            <a href="/">Shoes</a>
            <a href="/">Boots</a>
            <a href="/">Sandals</a>
          </CwPrimaryNavigationMenu>
        </CwPrimaryNavigationItem>
        <CwPrimaryNavigationItem>
          Company
          <CwPrimaryNavigationMenu slot="menu">
            <a href="/">About</a>
            <a href="/">Careers</a>
          </CwPrimaryNavigationMenu>
        </CwPrimaryNavigationItem>
      </CwPrimaryNavigation>
    </CwHeader>

    <style>{css}</style>
  </>
);

Roadmap

Badges on action triggers (e.g. a notification count on the bell, an item count on the cart) are not yet supported — deferred out of this component’s initial scope. When picked up, the intended approach is the same corner-overlay technique <cw-button> already uses for slotted <cw-badge> content (src/components/button/button.styles.ts):

.button ::slotted(cw-badge) {
  position: absolute;
  top: 0;
  right: 0;
  translate: 50% -50%;
  pointer-events: none;
}

The equivalent for <cw-header> would let a <cw-badge> be slotted alongside (or into) an action slot’s content and absolutely positioned against that action’s trigger.

Importing

If you’re using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use any of the following snippets to cherry pick this component.

Script Import Bundler React

To import this component from the CDN using a script tag:

<script type="module" src="https://cdn.jsdelivr.net/npm/@cordwainer/cw-elements@1.5.2/cdn/components/header/header.js"></script>

To import this component from the CDN using a JavaScript import:

import 'https://cdn.jsdelivr.net/npm/@cordwainer/cw-elements@1.5.2/cdn/components/header/header.js';

To import this component using a bundler:

import '@cordwainer/cw-elements/dist/components/header/header.js';

To import this component as a React component:

import CwHeader from '@cordwainer/cw-elements/dist/react/header';

Slots

Name Description
logo A <cw-logo>.
primary-navigation A <cw-primary-navigation>.
utility Content for the utility action panel, opened via a gear icon trigger.
search Content for the search action panel, opened via a search icon trigger.
notification Content for the notification action panel, opened via a bell icon trigger.
user Content for the user/account action panel, opened via a person icon trigger.
cart Content for the cart action panel, opened via a cart icon trigger.
secondary-navigation A <cw-secondary-navigation>, rendered on its own row below everything else.

Learn more about using slots.

Properties

Name Description Reflects Type Default
activePanel The name of the currently open action panel, or null if none is open. Not reflected as an attribute — null is a legitimate, frequently-reached value here (no panel open), which doesn’t play well with CwElement’s reflected-attribute “morph fix” (src/internal/cw-element.ts), designed around properties whose first non-null value is a permanent default rather than a value that legitimately toggles back to null at runtime. HeaderAction['name'] | null null
mobileBreakpoint
mobile-breakpoint
The viewport width, in pixels, below which the header switches to its stacked mobile layout. number 768
updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about attributes and properties.

Events

Name React Event Description Event Detail
cw-panel-show onCwPanelShow Emitted when an action panel opens. { name: String }
cw-panel-after-show onCwPanelAfterShow Emitted after an action panel opens and all animations are complete. { name: String }
cw-panel-hide onCwPanelHide Emitted when an action panel closes. { name: String }
cw-panel-after-hide onCwPanelAfterHide Emitted after an action panel closes and all animations are complete. { name: String }

Learn more about events.

Methods

Name Description Arguments
showPanel() Opens the named action panel. No-op if that slot has no content. name: HeaderAction['name']
hidePanel() Closes the currently open action panel, if any. -

Learn more about methods.

Custom Properties

Name Description Default
--panel-width The width of an open action panel above mobile-breakpoint. Below it, panels are always full width. Defaults to 50%.

Learn more about customizing CSS custom properties.

Parts

Name Description
base The component’s base wrapper, a <header> landmark element.
logo The container that wraps the slotted logo.
primary-navigation The container that wraps the slotted primary navigation.
actions The container that wraps the action triggers.
trigger Each action trigger, a <cw-icon-button>.
trigger--utility The utility action trigger.
trigger--search The search action trigger.
trigger--notification The notification action trigger.
trigger--user The user action trigger.
trigger--cart The cart action trigger.
trigger__base A trigger’s exported base part.
indicator The line that highlights the currently open action trigger.
panel The panel shown below the header when an action trigger is open.
secondary-navigation The container that wraps the slotted secondary navigation.

Learn more about customizing CSS parts.

Animations

Name Description
header.show The animation to use when an action panel opens.
header.hide The animation to use when an action panel closes.

Learn more about customizing animations.

Dependencies

This component automatically imports the following dependencies.

  • <cw-icon>
  • <cw-icon-button>