Skip to main content
Urbicon UI
Back to Recipes

Page Header

Top-of-page heading pattern with eyebrow, title, subtitle, and action area. Pure Tailwind composition — no library component needed. Four variants for list pages, detail pages with breadcrumb, tabbed pages, and form pages.

Live Preview

List page

Property management

Apartments

Master data of all managed apartments — addresses, occupancy, last inspection.

Detail page with breadcrumb

Building 12

Sunset Heights

18 apartments · 4 floors · occupied since 1987.

Tab page

Settings

Manage your account, billing, and team preferences.

Form page (h2 inside dialog)

Invite a new member

They'll receive an email with a link to join your workspace.

Features

  • List-page header with eyebrow, title, subtitle, and a primary action on the right
  • Detail-page header with a leading Breadcrumb above the title
  • Tab-page header where the heading row sits above a Tab strip
  • Form-page header with secondary actions (Cancel/Save) on the right
  • Responsive layout — actions wrap below the heading on narrow viewports
  • Heading-level prop pattern (h1 vs h2 vs h3) for nesting inside larger layouts

Code

List-page header

<header class="mb-6 flex flex-col gap-4 sm:mb-8">
  <div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between sm:gap-6">
    <div class="min-w-0 flex-1">
      <p class="text-text-tertiary mb-1 text-xs font-medium tracking-wide uppercase">
        Property management
      </p>
      <h1 class="text-text-primary text-3xl font-semibold tracking-tight">Apartments</h1>
      <p class="text-text-secondary mt-1.5 max-w-2xl text-sm leading-relaxed sm:text-base">
        Master data of all managed apartments.
      </p>
    </div>
    <div class="flex flex-wrap items-center gap-2 sm:shrink-0">
      <Button intent="primary" onclick={openCreate}>New apartment</Button>
    </div>
  </div>
</header>

Detail-page header with Breadcrumb

<header class="mb-6 flex flex-col gap-4 sm:mb-8">
  <div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between sm:gap-6">
    <div class="min-w-0 flex-1">
      <div class="mb-2">
        <Breadcrumb items={[
          { label: 'Buildings', href: '/buildings' },
          { label: 'Sunset Heights' }
        ]} />
      </div>
      <p class="text-text-tertiary mb-1 text-xs font-medium tracking-wide uppercase">Building 12</p>
      <h1 class="text-text-primary text-3xl font-semibold tracking-tight">Sunset Heights</h1>
      <p class="text-text-secondary mt-1.5 max-w-2xl text-sm leading-relaxed sm:text-base">
        18 apartments · 4 floors · occupied since 1987.
      </p>
    </div>
    <div class="flex flex-wrap items-center gap-2 sm:shrink-0">
      <Button variant="ghost" intent="neutral">Archive</Button>
      <Button intent="primary">Edit</Button>
    </div>
  </div>
</header>

Tab-page header — heading row + Tab strip

<header class="mb-6 flex flex-col gap-4 sm:mb-8">
  <div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between sm:gap-6">
    <div class="min-w-0 flex-1">
      <h1 class="text-text-primary text-3xl font-semibold tracking-tight">Settings</h1>
      <p class="text-text-secondary mt-1.5 max-w-2xl text-sm leading-relaxed sm:text-base">
        Manage your account, billing, and team preferences.
      </p>
    </div>
    <div class="flex flex-wrap items-center gap-2 sm:shrink-0">
      <Button variant="outlined" intent="neutral">Export</Button>
    </div>
  </div>
  <Tab bind:value={activeTab} variant="line">
    {#snippet tabs()}
      <TabItem value="overview">Overview</TabItem>
      <TabItem value="billing">Billing</TabItem>
      <TabItem value="team">Team</TabItem>
    {/snippet}
  </Tab>
</header>

Form-page header — h2 for nested context

<!-- Inside a Dialog, Drawer, or any context that already has an outer <h1>: -->
<header class="mb-6 flex flex-col gap-4 sm:mb-8">
  <div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between sm:gap-6">
    <div class="min-w-0 flex-1">
      <h2 class="text-text-primary text-2xl font-semibold tracking-tight">
        Invite a new member
      </h2>
      <p class="text-text-secondary mt-1.5 max-w-2xl text-sm leading-relaxed sm:text-base">
        They'll receive an email with a link to join your workspace.
      </p>
    </div>
    <div class="flex flex-wrap items-center gap-2 sm:shrink-0">
      <Button variant="ghost" intent="neutral" onclick={onCancel}>Cancel</Button>
      <Button intent="primary" onclick={onSubmit}>Send invite</Button>
    </div>
  </div>
</header>

Best Practices

Pick the right heading level

Use <h1> when the header is the single top-of-page heading. Drop to <h2> when you're inside a Dialog, Drawer, or a route that already has an outer h1 — a page should have exactly one h1.

Actions on the right, wrapping below on narrow viewports

flex-col gap-4 sm:flex-row sm:justify-between gives you the responsive default: heading + actions stack on mobile, sit side-by-side from sm: upward. The actions container uses flex-wrap so two-button groups don't push the heading off-screen.

Eyebrow vs. Breadcrumb — pick one

Eyebrow ("Property management") is a category label; Breadcrumb is a navigation trail. Use eyebrow for context that doesn't navigate; use Breadcrumb when each ancestor is a real route. Stacking both above the title is visually noisy — pick the one that earns its weight.

Subtitle should describe the page, not repeat the title

"Apartments — list of apartments" is filler. A good subtitle either narrows the scope ("…managed by your team this quarter") or tells the user what they can do here. If you can't write a useful one, leave it out.

Why this is a recipe, not a component

The header is pure layout composition with zero state, zero variants, and zero behaviour. Wrapping it in a component would hide the markup from the consumer without saving any logic — and would lock the structure when projects routinely need small variations (different eyebrow position, a status pill next to the title, a custom action row). Copy the snippet that matches your page; adapt it in place.