Skip to content
ShineUI
Install

PageHeader

How every screen opens.

<sui-page-header>PanelsStable

About

The first thing on a view: a title with an optional eyebrow, status tag and meta line, actions on the trailing side, a breadcrumb above and a tab row below. Use it once per screen, over a list, a record or a settings form, so every page opens the same way and can take focus after a view change.

Usage

<sui-page-header heading="Gateway migration"> <sui-tag slot="tag" label="On track" severity="positive" size="sm"></sui-tag> <span slot="meta">Platform team. Started Aug 4. Target Sep 30.</span> <sui-button slot="actions" variant="outline" icon="fa-solid fa-share-nodes" label="Share"></sui-button> <sui-icon-button slot="actions" variant="outline" icon="fa-solid fa-ellipsis" aria-label="More"></sui-icon-button> <sui-button slot="primary" variant="accent" icon="fa-solid fa-plus" label="New task"></sui-button> <sui-tabs slot="tabs" id="ph-tabs" aria-label="Project sections"> <sui-tab label="Overview"></sui-tab> <sui-tab label="Tasks" badge="12"></sui-tab> <sui-tab label="Updates" badge="3"></sui-tab> </sui-tabs> </sui-page-header> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-page-header"); el.addEventListener("suiBack", (e) => console.log(e.detail)); </script>
import { SuiPageHeader } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return ( <SuiPageHeader heading="Gateway migration" onSuiBack={(e) => console.log(e.detail)}> <sui-tag slot="tag" label="On track" severity="positive" size="sm"></sui-tag> <span slot="meta">Platform team. Started Aug 4. Target Sep 30.</span> <sui-button slot="actions" variant="outline" icon="fa-solid fa-share-nodes" label="Share"></sui-button> <sui-icon-button slot="actions" variant="outline" icon="fa-solid fa-ellipsis" aria-label="More"></sui-icon-button> <sui-button slot="primary" variant="accent" icon="fa-solid fa-plus" label="New task"></sui-button> <sui-tabs slot="tabs" id="ph-tabs" aria-label="Project sections"> <sui-tab label="Overview"></sui-tab> <sui-tab label="Tasks" badge="12"></sui-tab> <sui-tab label="Updates" badge="3"></sui-tab> </sui-tabs> </SuiPageHeader> ); }
<script setup> import { SuiPageHeader } from "@shineui/vue"; import "@shineui/core/shineui.css"; const onChange = (e) => console.log(e.detail); </script> <template> <SuiPageHeader heading="Gateway migration" @sui-back="onChange"><sui-tag slot="tag" label="On track" severity="positive" size="sm"></sui-tag> <span slot="meta">Platform team. Started Aug 4. Target Sep 30.</span> <sui-button slot="actions" variant="outline" icon="fa-solid fa-share-nodes" label="Share"></sui-button> <sui-icon-button slot="actions" variant="outline" icon="fa-solid fa-ellipsis" aria-label="More"></sui-icon-button> <sui-button slot="primary" variant="accent" icon="fa-solid fa-plus" label="New task"></sui-button> <sui-tabs slot="tabs" id="ph-tabs" aria-label="Project sections"> <sui-tab label="Overview"></sui-tab> <sui-tab label="Tasks" badge="12"></sui-tab> <sui-tab label="Updates" badge="3"></sui-tab> </sui-tabs></SuiPageHeader> </template>
import { Component } from "@angular/core"; import { SuiPageHeader } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiPageHeader], template: `<sui-page-header heading="Gateway migration" (suiBack)="onChange($event)"><sui-tag slot="tag" label="On track" severity="positive" size="sm"></sui-tag> <span slot="meta">Platform team. Started Aug 4. Target Sep 30.</span> <sui-button slot="actions" variant="outline" icon="fa-solid fa-share-nodes" label="Share"></sui-button> <sui-icon-button slot="actions" variant="outline" icon="fa-solid fa-ellipsis" aria-label="More"></sui-icon-button> <sui-button slot="primary" variant="accent" icon="fa-solid fa-plus" label="New task"></sui-button> <sui-tabs slot="tabs" id="ph-tabs" aria-label="Project sections"> <sui-tab label="Overview"></sui-tab> <sui-tab label="Tasks" badge="12"></sui-tab> <sui-tab label="Updates" badge="3"></sui-tab> </sui-tabs></sui-page-header>`, }) export class Example { onChange(e: CustomEvent) { console.log(e.detail); } }

Title, tag, meta, actions and tabs

The project page. heading is the title, tag sits beside it, meta under it. Secondary actions go in actions and the one accent action in primary, last, at the end of the row. tabs holds a sui-tabs whose sui-tab children are empty; the page draws the panel under the header and switches it on suiChange.

Platform team. Started Aug 4. Target Sep 30.
Summary, milestones and the latest update.
<sui-page-header heading="Gateway migration"> <sui-tag slot="tag" label="On track" severity="positive" size="sm"></sui-tag> <span slot="meta">Platform team. Started Aug 4. Target Sep 30.</span> <sui-button slot="actions" variant="outline" icon="fa-solid fa-share-nodes" label="Share"></sui-button> <sui-icon-button slot="actions" variant="outline" icon="fa-solid fa-ellipsis" aria-label="More"></sui-icon-button> <sui-button slot="primary" variant="accent" icon="fa-solid fa-plus" label="New task"></sui-button> <sui-tabs slot="tabs" id="ph-tabs" aria-label="Project sections"> <sui-tab label="Overview"></sui-tab> <sui-tab label="Tasks" badge="12"></sui-tab> <sui-tab label="Updates" badge="3"></sui-tab> </sui-tabs> </sui-page-header> <div id="ph-panel" style="padding:16px 0;font:var(--w-regular) var(--text-md)/1.5 var(--font-sans);color:var(--ink-muted)">Summary, milestones and the latest update.</div>
// setup(root): properties and events setup(root) { const panel = root.querySelector('#ph-panel'); const copy = ['Summary, milestones and the latest update.', 'Twelve open tasks, grouped by status.', 'Three project updates, newest first.']; root.querySelector('#ph-tabs').addEventListener('suiActiveChange', (e) => { panel.textContent = copy[e.detail] || ''; }); }

breadcrumb sits above the title and back draws an arrow named Back before it, for a record reached from a list; its click emits a cancelable suiBack first, so a host that navigates in memory can preventDefault. eyebrow is a short uppercase label over the title. heading-level changes the element only, not the size.

LRK-142 Opened Aug 28 by Tom Gordon. Due Sep 12. Back goes to the task list here; the click is intercepted below.
<sui-page-header id="ph-record" heading="Raise the gateway quota" heading-level="2" back="#/tasks"> <sui-breadcrumb slot="breadcrumb" id="ph-crumbs"></sui-breadcrumb> <span slot="eyebrow">LRK-142</span> <sui-tag slot="tag" label="In review" severity="info" size="sm"></sui-tag> <span slot="meta">Opened Aug 28 by Tom Gordon. Due Sep 12.</span> <sui-copy-button slot="actions" value="https://larkspur.example/tasks/142" label="Copy link" copied-label="Copied" variant="outline"></sui-copy-button> <sui-button slot="primary" variant="accent" icon="fa-regular fa-circle-check" label="Mark done"></sui-button> </sui-page-header> <span id="ph-record-log" style="display:block;margin-top:12px;font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Back goes to the task list here; the click is intercepted below.</span>
// setup(root): properties and events setup(root) { root.querySelector('#ph-crumbs').items = [ { label: 'Tasks', href: '#/tasks' }, { label: 'LRK-142' }, ]; const log = root.querySelector('#ph-record-log'); root.querySelector('#ph-record').addEventListener('suiBack', (e) => { e.preventDefault(); log.textContent = 'suiBack was vetoed, so the page stayed put.'; }); root.querySelector('#ph-crumbs').addEventListener('click', (e) => { if (e.target.closest('a')) { e.preventDefault(); log.textContent = 'The breadcrumb link was intercepted the same way.'; } }); }

A title that is markup

Leave heading unset and fill the default slot when the title is more than text: here a sui-inplace, which takes the title font from the header, so the record renames in place at the page size. meta takes nodes as well as text.

LRK-142 opened this Aug 28
<sui-page-header> <span slot="eyebrow">LRK-142</span> <sui-inplace value="Raise the gateway quota" placeholder="Untitled task" aria-label="Task title"></sui-inplace> <sui-persona slot="meta" name="Tom Gordon" size="xs" weight="medium"></sui-persona> <span slot="meta">opened this Aug 28</span> <sui-icon-button slot="actions" variant="outline" icon="fa-solid fa-ellipsis" aria-label="More"></sui-icon-button> </sui-page-header>

Narrow columns

Below 680px of its own width the header stacks: the actions drop under the title and align to the start. It measures the element, not the viewport, so a header inside a pane or a dialog stacks when the pane is narrow.

Eight people, two invited.
<div style="max-width:520px"> <sui-page-header heading="Members"> <span slot="meta">Eight people, two invited.</span> <sui-button slot="actions" variant="outline" icon="fa-solid fa-filter" label="Filter"></sui-button> <sui-button slot="actions" variant="outline" icon="fa-solid fa-download" label="Export"></sui-button> <sui-button slot="primary" variant="accent" icon="fa-solid fa-user-plus" label="Invite members"></sui-button> </sui-page-header> </div>

Moving focus to the title

The title carries tabindex="-1", so it never sits in the tab order but can take focus. Call setFocus() after a view change and a screen reader announces the new page from its heading; the ring shows only for keyboard focus.

Four unread.
Press the button, or Tab to it and press Enter.
<div style="display:flex;flex-direction:column;gap:14px"> <sui-page-header id="ph-focus" heading="Inbox"> <span slot="meta">Four unread.</span> </sui-page-header> <div style="display:flex;align-items:center;gap:12px"> <sui-button id="ph-focus-btn" variant="outline" label="Focus the title"></sui-button> <span id="ph-focus-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Press the button, or Tab to it and press Enter.</span> </div> </div>
// setup(root): properties and events setup(root) { const head = root.querySelector('#ph-focus'); const log = root.querySelector('#ph-focus-log'); root.querySelector('#ph-focus-btn').addEventListener('click', async () => { await head.setFocus(); log.textContent = document.activeElement && document.activeElement.classList.contains('title') ? 'The title has focus.' : 'Focus did not move.'; }); }

Keyboard

KeyAction
TabMoves through the back link when back is set, then the controls in the breadcrumb, actions, primary and tabs slots in document order. The title carries tabindex="-1", so it is never a tab stop.
EnterOn the back link, emits a cancelable suiBack and follows the href unless a listener cancels it.
setFocus()Not a key: the method moves focus to the title so a screen reader announces the new page, and the ring shows only for keyboard focus.

API

<sui-page-header>

Properties

PropertyAttributeTypeDefaultDescription
backbackstringstring | undefinedHref of a back control drawn before the title, for a record reached from a list.
headingheadingstringstring | undefinedTitle text, the string form. For markup, leave it unset and fill the default slot.
headingLevelheading-levelSuiPageHeaderLevel1 | 2 | 31Heading level of the title element, 1 to 3. The size does not change with it.

Events

EventDetailReactVueDescription
suiBackvoidonSuiBack@sui-backFires when the back control is clicked, before the browser follows back. Cancelable: preventDefault() keeps the page where it is, for a host that navigates in memory.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves focus to the title, for a host that moves focus after a view change. It does not scroll: the header is the first thing in its view, and a frame embedded in a longer page must not drag that page to itself.

Slots

SlotDescription
(default)The title as markup, an inplace editor or text with a glyph. Leave heading unset.
actionsSecondary actions on the trailing side, before the primary one.
breadcrumbA sui-breadcrumb above the title.
eyebrowShort uppercase label above the title: a record id, a section name.
metaOne line under the title: text, or nodes such as a persona and a date.
primaryThe one accent action, last on the trailing side.
tabsA tab row flush with the header's bottom edge, drawn over the header's own rule. Give it a sui-tabs whose sui-tab children are empty and draw the panels under the header.
tagStatus beside the title, typically a small sui-tag.

Types

SuiPageHeaderLevel typeHeading level of the title: 1 on a page, 2 or 3 inside a region that already has one.
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also