Page Header

@use 'sprucecss/scss/spruce' as *;

.page-header {
  border-block-end: config('border-width-base', $display) solid color('border');
  @include layout-stack('s');

  &__breadcrumb {
    font-size: config('font-size-sm', $typography);

    ol {
      @include clear-list;
      @include layout-cluster('xxs');
    }

    li {
      align-items: center;
      display: inline-flex;
      gap: spacer('xxs');
      margin: 0;
    }

    li + li::before {
      color: color('muted');
      content: '/';
    }

    a {
      color: color('muted');
      text-decoration: none;

      &:hover {
        color: color('primary');
        text-decoration: underline;
      }
    }

    [aria-current='page'] {
      color: color('heading');
      font-weight: 500;
    }
  }

  &__main {
    align-items: flex-end;
    display: flex;
    flex-wrap: wrap;
    gap: spacer('s');
    justify-content: space-between;
  }

  &__heading {
    @include layout-stack('xxs');
  }

  &__title-row {
    @include layout-cluster('xs');
  }

  &__title {
    font-size: font-size('h3');
    margin: 0;
  }

  &__status {
    background-color: color-alpha('muted', 0.15);
    border-radius: radius('pill');
    color: color('heading');
    font-size: 0.8125rem;
    font-weight: 700;
    line-height: 1;
    padding: 0.4em 0.75em;

    &--success {
      background-color: color-alpha('success', 0.15, 'alert');
    }

    &--warning {
      background-color: color-alpha('warning', 0.25, 'alert');
    }
  }

  &__meta {
    color: color('muted');
    display: flex;
    flex-wrap: wrap;
    font-size: config('font-size-sm', $typography);
    gap: spacer('xxs') spacer('xs');

    span + span::before {
      content: '·';
      margin-inline-end: spacer('xs');
    }
  }

  &__actions {
    @include layout-cluster('xs');
  }

  &__tabs {
    margin-block-end: calc(#{config('border-width-base', $display)} * -1);
    overflow-x: auto;

    ul {
      @include clear-list;
      display: flex;
      gap: spacer('m');
    }

    li {
      margin: 0;
    }

    a {
      align-items: center;
      border-block-end: 2px solid transparent;
      color: color('muted');
      display: inline-flex;
      font-weight: 500;
      gap: spacer('xxs');
      padding-block: spacer('xs');
      text-decoration: none;
      white-space: nowrap;
      @include transition;

      &:hover {
        color: color('heading');
      }

      &[aria-current='page'] {
        border-color: color('primary');
        color: color('primary');
      }
    }
  }

  &__tab-count {
    background-color: color('surface-muted');
    border: config('border-width-base', $display) solid color('border');
    border-radius: radius('pill');
    font-size: 0.75rem;
    line-height: 1;
    padding: 0.2em 0.5em;
  }
}
<header class="page-header">
    <nav class="page-header__breadcrumb" aria-label="Breadcrumb">
        <ol role="list">
            <li><a href="#">Dashboard</a></li>
            <li><a href="#">Orders</a></li>
            <li><span aria-current="page">#10482</span></li>
        </ol>
    </nav>
    <div class="page-header__main">
        <div class="page-header__heading">
            <div class="page-header__title-row">
                <h1 class="page-header__title">Order #10482</h1>
                <span class="page-header__status page-header__status--success">Paid</span>
                <span class="page-header__status page-header__status--warning">Unfulfilled</span>
            </div>
            <p class="page-header__meta">
                <span>Placed on <time datetime="2026-10-03T14:21">October 3, 2026 at 2:21 PM</time></span>
                <span>3 items</span>
                <span>Online Store</span>
            </p>
        </div>
        <div class="page-header__actions">
            <a class="btn btn--outline-secondary btn--sm" href="#">Refund</a>
            <a class="btn btn--outline-secondary btn--sm" href="#">Print</a>
            <a class="btn btn--primary btn--sm" href="#">Fulfill items</a>
        </div>
    </div>
    <nav class="page-header__tabs" aria-label="Order sections">
        <ul role="list">
            <li><a href="#" aria-current="page">Overview</a></li>
            <li><a href="#">Fulfillment</a></li>
            <li><a href="#">Payments</a></li>
            <li><a href="#">Timeline <span class="page-header__tab-count">8</span></a></li>
        </ul>
    </nav>
</header>

Page Header

The top of an admin page: breadcrumb, title with status badges, meta information, the primary actions and the section tabs.

Anatomy

  • .page-header__breadcrumb: an ordered list of links; the current page is a span with aria-current="page".
  • .page-header__main: the heading group (__title-row with __status badges and __meta) and the __actions. The actions wrap under the title on narrow screens.
  • .page-header__tabs: the sub-navigation of the record. These are links to separate pages, not ARIA tabs; use the Tabs component when the panels are on the same page.

Accessibility and Usability

  • Both navigations have a distinct aria-label ("Breadcrumb", "Order sections"), so screen reader users can tell the landmarks apart.
  • The status badges are plain text ("Paid", "Unfulfilled"); the color only reinforces them.
  • Keep one primary button; the less important actions use the outline variant. Move rarely used actions into a Context Menu.

Technical Details

  • The breadcrumb separator is generated with CSS (li + li::before), so it is not read out.
  • The tab list scrolls horizontally when it doesn't fit; the active tab's border overlaps the header's bottom border.
  • The status colors use the alert tokens (color-alpha('success', 0.15, 'alert')), so they follow the dark mode and every style.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.