Design System
Components

PageHeader

Opens a content page — icon, a two-line muted/strong title, a subtitle and tab pills.

Figma description · page-headerOpen in Figma ↗

A centred introductory block that introduces a page or category with an icon, a two-line heading, a subtitle, and a horizontally scrollable tab-pill row. Acts as the general thematic header — it sets the topic and tone for an entire page.

Use for: The top of a content page, directly below the top bar — to orient the user, establish the page theme, and provide immediate category filtering.

Not for: Outcome or status messages after a user action — use page-title instead. Not for section headings within a page — use section with a header slot instead. Not for modal or sheet titles.

Choosing: Use state=default on light surfaces — renders with a white card surface, dark heading text, and subtle muted colour for the secondary heading words. Use state=Inverse on dark or glass backgrounds — renders with a glass white surface (20% opacity), and all text tokens shift to inverse white. The tab-pills inside the block automatically use inverse=true tokens on the Inverse state and inverse=false on the default state — do not manually override individual tab-pill colour schemes inside this component.

Rules: Use exactly one page-header per screen. Place it directly below the top bar and outside any section or scroll slot. The heading supports a two-line structure with a muted word and a strong word per line — edit via the .title-block text properties. The icon slot is 56px; do not resize it. The subtitle is optional but recommended for context. The tab-pill row scrolls horizontally when there are more tabs than the viewport allows; always keep exactly one tab-pill selected.

page-header vs page-title: page-header introduces — it is a general thematic header that opens a page and sets context. page-title concludes — it is a generic informative message or a specific scenario outcome (success, error, warning). If the block orients the user at the start of a page, use page-header. If the block communicates the result of an activity, use page-title.

Discover Thermostats. Built for Intelligence

Your home learns your rhythm — adjusting temperature, saving energy, and creating comfort before you even notice.

Props
import SwiftUI
import EandTokens

// page-header: white r20 card, padding 24/16, centred. Two-tone title bound to the
// typography/heading/lg variables (28 / 120%), body/md subtitle, then 48pt tab pills.
struct PageHeader: View {
  let lines: [(muted: String, strong: String)]
  let subtitle: String
  let tabs: [String]
  @State private var selected = 0
  var body: some View {
    VStack(spacing: EandSpacing.md) {
      VStack(spacing: EandSpacing.xs) {
        ForEach(lines.indices, id: \.self) { i in
          (Text(lines[i].muted + " ").foregroundColor(EandColor.textDefaultSubtle)
            + Text(lines[i].strong).foregroundColor(EandColor.textDefaultDefault))
            .font(.system(size: EandTypography.headingXl.size, weight: .bold))   // 28pt
        }
        Text(subtitle)
          .font(.system(size: EandTypography.bodyMd.size))
          .foregroundColor(EandColor.textDefaultSubtle)
          .multilineTextAlignment(.center)
          .padding(.horizontal, EandSpacing.spacing2xl)
      }
      ScrollView(.horizontal, showsIndicators: false) {
        HStack(spacing: EandSpacing.xs) {
          ForEach(tabs.indices, id: \.self) { i in
            Text(tabs[i])
              .font(.system(size: EandTypography.titleXs.size, weight: .semibold))
              .foregroundColor(i == selected ? EandColor.tabInverseTextSelected : EandColor.tabDefaultTextDefault)
              .padding(.horizontal, EandSpacing.lg).frame(minWidth: 72, minHeight: 48)
              .background(i == selected ? EandColor.tabInverseSurfaceSelected : EandColor.tabDefaultSurfaceDefault)
              .clipShape(Capsule())
              .onTapGesture { selected = i }
          }
        }
      }
    }
    .padding(.vertical, EandSpacing.spacing2xl).padding(.horizontal, EandSpacing.lg)
    .background(EandColor.surfaceBaseInverse)
    .clipShape(RoundedRectangle(cornerRadius: EandRadius.radius7))
  }
}

When to use

  • Use at the top of a content page, directly below the top bar, to set the page's theme and give immediate category filtering. One per screen, outside any Section.
  • Don't use for the result of an action (success, failure, confirmation) — that is PageTitle. Not for headings inside a page (use Section), and not for sheet or modal titles.

Anatomy

  • Icon — a 56px slot, typically a 3D illustration. Do not resize it.
  • Title — bold 28px at 120%, up to two lines. Each line pairs a muted word with a strong word: { muted: 'Discover', strong: 'Thermostats.' }.
  • Subtitle — body/md, centred. Optional but recommended.
  • Tabs — 48px pills in a row that scrolls sideways. Exactly one is always selected.

Pass inverse on dark or image backgrounds: the card becomes 20% white glass and every text token flips to its inverse. The tab pills follow the header — don't restyle them yourself.

Props

PropTypeDefaultDescription
titlePageHeaderLine[]—Up to two lines; each pairs a muted word with a strong word.
as?'h1' | 'h2'h1
defaultValue?number0
icon?ReactNode—56px icon slot — typically a 3D illustration. Do not resize.
inverse?booleanfalseFor dark or glass backgrounds (Figma State=Inverse).
onTabChange?((index: number) => void)—
subtitle?ReactNode—
tabs?string[]—
value?number—

Design

Figma source: page-header — etisalat Consumer App DSL V1.2

  • Card padding 24 × 16 (spacing/2xl, spacing/lg); content gap 12 (spacing/md); title-to-subtitle 4.
  • The title binds the typography/heading/lg variables (28px, 120%, tight tracking), not the 24px heading/lg text style that Section applies.
  • Default surface is surface/base/inverse (white) at border-radius/7 (20px); inverse is surface/glass/white/lg at border-radius/8 (24px), as the published symbol has them.
  • In RTL the title words, subtitle and tab row mirror automatically.

On this page