Skip to content

PlHeader

The bar across the top of a page: a brand at one end, the actions at the other, and whatever belongs in the middle. A real <header>, which is what makes it the banner landmark.

React
tsx
import { PlButton, PlHeader } from 'plass-ui';

<PlHeader brand={<Logo />} actions={<PlButton size="sm">Sign in</PlButton>}>
  <Nav />
</PlHeader>;
dart
import 'package:plass_ui/plass_ui.dart';

PlHeader(
  brand: const <Widget>[Text('Acme')],
  actions: <Widget>[PlButton(onPressed: signIn, child: const Text('Sign in'))],
  child: navigation,
);

Props

PropTypeDefaultDescription
variantshared'solid' | 'glass' | 'ghost''glass'What the sheet is made of. The bar is never dyed — what is on it arrives with colours of its own
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The bar's height floor, its gutter and the air around its slots. As on PlBox, size here is the size of the sheet
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
densityshared'default' | 'compact''default'Padding only — never the height, never the type scale
elevationshared0 | 1 | 2 | 30Drop shadow depth. 0 is the default and it is flat: a header is attached to the top of the window rather than floating over it
brandReactNodeThe leading slot: the logo, the product's name, the thing that is the same on every page
actionsReactNodeThe trailing slot: the account menu, the theme switch, the call to action. End-aligned, so a row of buttons needs no wrapper
alignshared'start' | 'center' | 'end''start'Where the middle slot sits. center is on the bar's own midline, not in the space left over
positionshared'static' | 'sticky' | 'fixed''sticky'How the bar sits in the page's scroll, spelled the way CSS spells it
dividerbooleantrueDraws a hairline along the bottom edge. On by default: a pinned bar has content passing underneath it at every moment
maxWidthPlassResponsive<'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'none' | number | string>'none'Holds the row of slots to a measure and centres it while the sheet still spans the window. The same ladder PlContainer's maxWidth uses
paddedbooleantrueThe gutter down each side of the row
labelstringThe name the bar is announced by. Worth writing when a page has more than one <header> in it
renderuseRender.RenderPropRenders something other than a <header>. Rarely what you want: the bar at the top of a page is a banner, and the tag is what says so
childrenReactNodeThe middle slot
PropTypeDefaultDescription
variantsharedPlassVariantPlassVariant.glassWhat the sheet is made of. The bar is never dyed — what is on it arrives with colours of its own
sizesharedPlassSizePlassSize.mdThe bar's height floor, its gutter and the air around its slots. As on PlBox, size here is the size of the sheet
colorsharedPlassColorPlassColor.primarySemantic colour role. Arbitrary colour values are not accepted
densitysharedPlassDensityPlassDensity.standardPadding only — never the height, never the type scale
elevationsharedint0Drop shadow depth. 0 is the default and it is flat: a header is attached to the top of the window rather than floating over it
brandList<Widget>?The leading slot: the logo, the product's name. A list because a slot is a row — the gap between a logo and the name beside it is the bar's to decide
actionsList<Widget>?The trailing slot: the account menu, the theme switch, the call to action. End-aligned, so a row of buttons needs no wrapper
alignsharedPlassAlignPlassAlign.startWhere the middle slot sits. center is on the bar's own midline, not in the space left over
dividerbooltrueDraws a hairline along the bottom edge. On by default: a pinned bar has content passing underneath it at every moment
maxWidthPlassResponsive<PlContainerWidth?>?nullHow wide the content is allowed to get. PlContainerWidth.rung(PlassSize) is a rung of the ladder and PlContainerWidth.pixels(double) is an exact width. null means no limit, including as an entry
paddedbooltrueThe gutter down each side of the row
semanticLabelString?The name a screen reader gives the bar. Naming it also makes it a region landmark; without a name it claims nothing, which is the framework's rule rather than this package's
childWidget?The middle slot

Every native <header> attribute passes straight through. color and title are excluded because both are Plass props here.

brand and actions are lists rather than single widgets, because a slot is a row: the gap between a logo and the name beside it is the bar's to decide. PlToolbar spells its two ends the same way.

What the shared axes (variant size color density elevation) mean across the library is in prop conventions.

PlHeader or PlToolbar

A PlToolbar is a row of controls anywhere on a screen, and it takes its height from its padding alone. A header is the page's banner: it has a height floor, a measure, a brand slot, and a place in a PlPageLayout, none of which mean anything on a row of controls beside a table.

Reach for the toolbar when what you have is controls. Reach for this when what you have is the top of a page.

Examples

The three slots

brand, children and actions, in that order. They are props rather than sub-components for PlCard's reason: the arrangement is fixed, and what a caller decides is what goes in each.

A slot that is empty draws nothing. A header with only a brand is one region, not three.

align

Where the middle sits. start packs it against the brand and is the default; end packs it against the actions.

center is the one worth explaining. Centring the middle in the space left over puts it wherever the brand happens to end, so a logo one character longer moves the navigation, which is exactly what a reader notices between two pages of the same site. Both ends are given equal shares instead, so the middle lands on the bar's own midline whatever is in them. An empty end still takes its half.

React

position

sticky is the default: the bar is held against the top of the window once the page has scrolled to it, and it stays in the flow, so nothing underneath has to be padded out of its way.

fixed takes it out of the flow entirely, inside a PlPageLayout that is answered for you, because the layout reserves the bar's height. static lets it scroll away with the page.

variant

The three materials, read the way a container reads them. The bar is never dyed: what is on it (a chip, a button, an avatar) arrives with colours of its own, and a tinted sheet would put every one of them on a background it was not chosen against.

divider is on by default and is what actually separates the bar from the content: a translucent sheet pinned over a scrolling page has content passing underneath it at every moment, and nothing marking its edge reads as part of that.

React

size

The bar's floor is a control of the same size with air above and below it. md is 64px, which is a 40px control with 12px either side. It is a floor and not a height: a bar whose content wraps grows, and keeps its padding while it does.

density moves the gutter and nothing else, as everywhere else in the library.

React

maxWidth

Holds the row of slots to a measure and centres it while the sheet still spans the window, which is what a site header on a wide screen almost always wants.

The same ladder and the same type a PlContainer takes, and one implementation behind all three, a bar whose measure did not line up with the container under it is the defect that prevents. It is responsive and takes any CSS length with it.It is responsive, and takes an exact width as well as a rung.

It is the same rem ladder PlContainer's maxWidth uses (xs 30 · sm 40 · md 48 · lg 64 · xl 80), so the logo and the first paragraph of the page under it sit on one edge rather than two that nearly agree.

React

Inside a PlPageLayout

The header registers itself with the layout, which measures it and writes what it takes out of the window onto the layout's root. That is what lets a sidebar holding its place start below a bar whose height nobody but the bar knows.

Nothing has to be passed for this. Outside a layout the registration goes nowhere and the bar is simply a bar.

Nothing has to be passed, and nothing has to be measured: the layout's Column has already left the band exactly what the header did not take, so a sidebar beside it starts under the bar by construction.

Differences from the React build

ReactFlutterWhy
positionA fixed or sticky element has to span something. A widget goes exactly where the screen puts it, and a bar that has to stay put belongs in the screen's own layout.
registering with the layoutA Column has already done that arithmetic; there is no height to write anywhere.
brand, actions as one nodeList<Widget>A slot is a row, and the gap inside it is the bar's to decide. The same shape PlToolbar uses.
maxWidth: 'none'maxWidth: nullDart's way of saying "no measure was named".
<header>, the banner landmarksemanticLabel and a region roleThere is no banner role in Flutter's semantics. A named bar is a region; an unnamed one claims nothing, because a region with no label describes nothing.
labelsemanticLabelFlutter's name, and here it does one more thing: it is what makes the bar a landmark at all.
renderThere is no tag to swap.
className, style, native attributesThere is no class list and no style attribute to pass through.

Accessibility

  • It renders a real <header>. At the top level of a document that is the banner landmark, which is what a screen reader's landmark list, a reader mode and a search engine all read.
  • label names the bar. Worth writing when a page has two of them, because "banner" twice tells a reader which is which not at all.
  • The bar claims no role="toolbar" and no role="navigation". The first is a promise about keyboard behaviour it does not implement; the second belongs to the <nav> a caller puts in the middle slot.
  • The slots are laid out but not reordered, so the reading order is the order they were written in.
  • semanticLabel names the bar and makes it a region landmark. Without one the bar claims nothing at all, which is deliberate: Flutter refuses a region with no label, because a landmark nobody can name is a landmark nobody can skip to.
  • There is no banner role to claim instead. A named region is the nearest true thing, and claiming a role the framework does not have would be worse than claiming none.
  • The bar claims no toolbar semantics. That is a promise about keyboard behaviour it does not implement.
  • The slots are laid out but not reordered, so the traversal order is the order they were written in.

Released under the MIT License