Page is the outermost layout for a route, setting where the title and page-level actions sit and how wide the content column is.
A page consists of the following components:
Page
PageHeader
header tag.PageHeaderIconTile
PageHeaderLabel
PageHeaderTitle
PageHeaderTitleAction
PageHeaderLabel if present).PageHeaderActions
Button components.PageHeaderDescription
PageHeaderTabs
Tabs component.PageHeaderExtra
PageBody
PageSection
PageRail
PageBodyContent
You can mix and match these components to create a simple or complex page header. At minimum you should have a PageHeader, PageHeaderTitle, and PageBody.
The components can be imported via:
Page composes into a few recurring layouts. In each, the PageHeader is full-bleed with a divider separating it from the body (use isFullScreen); what changes is the body width.
Set the content width once on the Page with bodyContentWidth — every PageBody and PageSection inherits it, so the layout holds together even when the header and body are assembled in different files. An individual PageBody or PageSection can still override the page default with its own contentWidth.
A full-bleed bordered header above a body split into sections — a Stepper, the step content, and the actions. Good for setup flows and long forms, where a narrower reading width keeps each step focused.
See the Wizard layout pattern for the full composition.
A full-bleed header above a full-bleed body — for canvas and builder surfaces that use the whole viewport. Use isFullScreen and leave the body at its default, so it inherits the page's full width.
An icon tile displayed to the left of the title.
A status label displayed to the right of the title.
A slot for an inline action displayed to the right of the title (after PageHeaderLabel if present) — typically an edit button or other affordance that operates on the page subject.
A slot for page-level content the header has no dedicated area for — a Banner about the page as a whole, or a placeholder standing in for one while it loads. It sits below the title row and the description and above the tabs.
Unlike PageHeaderTabs, which bleeds to the header's edges so the tab underline can run the full width, PageHeaderExtra stays inside the header's content band. Put page-level content here rather than in the tabs slot: the tabs slot needs its negative inline margin undone by hand, and the spacing has to be guessed.
The slot is spaced off the title block above and the tabs below by the header's block padding (pageHeaderPaddingBlock), so nothing needs to carry its own margin.
Content that belongs to the page's content rather than to the page itself goes in a PageSection at the top of PageBody instead. The test is whether it would still make sense if the page's tabs switched to another view — a banner about the whole record would, a banner about the table below it would not.
A header with tabs ends in the line the tab list draws under itself, and a full-bleed header draws that line whenever it has no tabs. A header that is neither — one whose last row is a PageHeaderExtra — has no line, and the way to get one used to be rendering an empty PageHeaderTabs for the side effect.
withDivider on the slot draws it instead. It is ignored when the header has tabs, since they draw that line themselves and a header cannot sensibly end in two, and it is redundant on a full-screen header, which already has one.
A band inside PageBody that stacks with its siblings — a wizard's Stepper, a page-level banner, a filter bar, the content itself, the trailing actions. Sections are how a page varies its content width down the page: each one takes its own contentWidth, and the rest of the page is unaffected.
A section can never be wider than the body that contains it, so leave PageBody unconstrained and constrain the sections.
Sections carry no padding of their own — PageBody already insets the page, and a section adding to it would double the inset. The only block spacing a section owns is the gap to a sibling section (pageSectionGap), so the first and last sections add nothing at the body's edges.
pageSectionGap is the space between any two adjacent things, and a divider counts as one of them. An undivided boundary is a single gap; a divided one is a gap above the line and a gap below.
divider draws a line on the boundary between a section and the next. It never renders after the last section, so it can't leave a trailing line.
contained — spans the section's content column. Use it when adjacent sections share a content width, so the line aligns with the text.extended — spans the body. Use it when the sections differ in width, or when the line marks a change of page region rather than a break in content.Either mode clears the content by one gap on both sides, so the line sits centred between the two sections whichever you pick.
A column beside the body's content — a wizard's vertical Stepper, a table of contents, a summary. It stands beside every section in the body, so it can stay in view while a long page scrolls, and is as wide as its content needs.
Put the rail inside PageBody and wrap the sections in a PageBodyContent.
Write the rail before PageBodyContent and it sits on the leading side; write it after and it sits on the trailing side. There's no placement prop, so a rail can't be drawn on one side while being read from the other.
Lead with content that helps someone move through the page, like a Stepper or a table of contents — it's read first, ahead of what it describes. Put content that comments on the page, like a summary, after.
A body can hold a rail on each side. Two is rarely right — it leaves the content squeezed between them.
The content keeps the width its preset asks for and the rail takes the space beside it. Where there isn't room for both, the content gives way:
narrow — the content stays centred and the rail sits in the space beside it.full — the rail always eats into the content.standard — the rail eats in until the viewport is wide enough to centre the content clear of it.PageBody spans the page when it holds a rail, so bodyContentWidth and contentWidth size the content column rather than the body. A PageSection can't be wider than that column, which is why divider="extended" draws the same line as contained here.
Below the narrowest width that fits both, the content stops shrinking and the page scrolls sideways.
isSticky keeps the rail in view while the content beside it scrolls, and releases it at the end of the content.
The rail sticks to whatever the app scrolls, so two things are worth knowing:
overflow other than visible between the rail and that scroll container disables sticky positioning, silently.pageRailStickyInsetBlockStart clears fixed chrome above the page. It defaults to the body's block padding.Keep a sticky rail short enough to fit the viewport. A taller one puts its own lower end out of reach while it's stuck.
PageRail renders a div, which suits a Stepper or anything else that brings its own semantics. Pass as="nav" with an aria-label when the rail holds links into the page, or as="aside" when its content is genuinely tangential.
Because the side comes from where the rail is written, reading order and visual order always agree.
PageBreadcrumbs is deprecated and will be removed in the next major release. It still renders — it lives as a sibling of PageHeader inside Page, not inside the header grid — so existing pages keep working without changes.
For new code, render the standalone Breadcrumbs component above the page header yourself instead.
The Page component can be full screen by passing the isFullScreen prop.
The Page component can be in a loading state by passing the isLoading prop.
When the page is full screen or has tabs, the page looks better with just the body in a loading state. This gives the user more of an anchor for the page. You can pass the isLoading prop to the PageBody component to achieve this.
Sets the default content width for every PageBody and PageSection on
the page, so the width can be configured once on the page rather than
repeated on each one. A contentWidth prop on an individual PageBody or
PageSection wins over this.
See PageBody's contentWidth for the available presets.
Whether the page should be full screen.
Whether the page is loading.
PlumaPageHeader extends BoxPlumaPageHeaderTitle extends TextDefault:1
The level of the heading (which maps to h1-h6 tags)
PlumaPageHeaderTitleAction extends BoxPlumaPageHeaderActions extends BoxPlumaPageHeaderDescription extends ParagraphPlumaPageHeaderTabs extends BoxPlumaPageHeaderExtra extends BoxWhether the header closes with a divider below this slot, the line a
PageHeaderTabs would otherwise have to be rendered — empty — to draw.
Ignored when the header has tabs, since the tabs draw that line themselves and a header cannot sensibly end in two. A full-screen header already draws it whenever it has no tabs, so this is for the header that is not full screen.
Default:'teal'
The color of the icon tile.
The name of the icon to render.
PlumaPageHeaderIcon extends IconWhether the filled version of an icon should be used, if available
PageHeaderLabel extends LabelDefault:true
Whether the label should render in a bold font weight
Default:grey
The visual style of the label. Note: When using status indicators, only red, yellow, green, blue, purple, and grey colors are supported.
Default:false
Whether the label should show a status indicator colored dot
PlumaPageBody extends BoxConstrains the body's content width. When omitted, the body inherits the
page width (standard, or full-bleed when the page isFullScreen).
standard — the standard page width (min 950px, max 1850px), even
under a full-screen header.narrow — max 800px, no minimum width.full — full bleed: stretches to the page with no minimum width. Use it
to break a single body out of a narrower page default.Whether the page body is loading.
PlumaPageSection extends BoxConstrains this section's content width, independently of the body and of the other sections. When omitted, the section fills the body.
See PageBody's contentWidth for the available presets. A section can
never be wider than the body that contains it — a preset whose minimum
exceeds the body clamps to the body rather than overflowing it — so leave
the body unconstrained and set the width per section.
Default:'none'
Draws a line on the boundary between this section and the next. Never renders after the last section, so it can't leave a trailing line.
contained — spans this section's content column.extended — spans the body, for a break between page regions rather
than between pieces of content. Draws the same line as contained in a
body that has a PageRail.PlumaPageBreadcrumbs extends BoxPlumaPageBodyContent extends BoxPlumaPageRail extends BoxWhether the rail stays in view while the content beside it scrolls, released at the end of the content.
The rail sticks to whatever the app scrolls, so an overflow other than
visible between the two disables it.