PageHeader
Opens a content page — icon, a two-line muted/strong title, a subtitle and tab pills.
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.
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
| Prop | Type | Default | Description |
|---|---|---|---|
title | PageHeaderLine[] | — | Up to two lines; each pairs a muted word with a strong word. |
as? | 'h1' | 'h2' | h1 | |
defaultValue? | number | 0 | |
icon? | ReactNode | — | 56px icon slot — typically a 3D illustration. Do not resize. |
inverse? | boolean | false | For 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/lgvariables (28px, 120%, tight tracking), not the 24pxheading/lgtext style thatSectionapplies. - Default surface is
surface/base/inverse(white) atborder-radius/7(20px); inverse issurface/glass/white/lgatborder-radius/8(24px), as the published symbol has them. - In RTL the title words, subtitle and tab row mirror automatically.