Props
Usage guidelines
- To inform a user about the overall content of a page
- As a header for an overlay surface like a Modal, Popover or OverlayPanel
- As page navigation
- As a title for sections inside of a page—there should only be one page header on a page
- As a toolbar
Best practices
Use only one primary action style in PageHeader. This should also be the only primary action on the page.
Use more than one primary action style in PageHeader, or include a primary action when there’s already a primary action elsewhere on the page. If there's already a primary action elsewhere on the page, PageHeader can have 1 or 2 secondary actions.
Plan for most PageHeaders to be full width. A maxWidth
should only be supplied when the content of the page is center aligned. The PageHeader’s padding should match the page’s overall padding.
Provide maxWidth
for PageHeader content that is different from the page content
Include an image when unique to the page content, such as a page dedicated to a developer’s apps
Include a profile avatar image in PageHeader, as the user avatar should be provided in the main app navigation
Keep additional help buttons and links to a minimum, choosing one source of help per PageHeader
Overload PageHeader with a help IconButton, help Link and info Tooltips. Too many sources of help on the page may confuse users. If there are multiple items to explain, use the help IconButton to open an
OverlayPanel with further help. If you want to lead users to external documentation, add a help Link with the helperLink
prop.
Accessibility
Labels
PageHeader has built-in components that require accessibility labels.
-
Dropdown (displayed in small screens) requires
dropdownAccessibilityLabel
-
IconButton requires
accessibilityLabel
,accessibilityControls
, andaccessibilityExpanded
viahelperIconButton
-
Link requires
accessibilityLabel
viahelperLink
Follow the accessibility guidelines for any other Gestalt component passed to primaryaction
, secondaryAction
or items
.
import { Button, Datapoint, Dropdown, PageHeader } from 'gestalt'; export default function PageHeaderExample() { return ( <PageHeader dropdownAccessibilityLabel="More options" items={[ <Datapoint key="impressions" size="md" title="Impressions" trend={{ value: 30, accessibilityLabel: 'Trending up' }} value="$1.25M" />, <Datapoint key="engagement" size="md" title="Engagement" trend={{ value: 5, accessibilityLabel: 'Trending up' }} value="10%" />, ]} primaryAction={{ component: <Button color="red" size="lg" text="Promote" />, dropdownItems: [ <Dropdown.Item key="promote" onSelect={() => {}} option={{ value: 'Promote', label: 'Promote' }} />, ], }} secondaryAction={{ component: <Button size="lg" text="View analytics" />, dropdownItems: [ <Dropdown.Link key="view-analytics" href="https://pinterest.com" option={{ value: 'View analytics', label: 'View analytics' }} />, ], }} title="Ads overview" /> ); }
Localization
Be sure to localize all text strings. Note that localization can lengthen text by 20 to 30 percent.
import { Button, Datapoint, Dropdown, PageHeader } from 'gestalt'; export default function PageHeaderLocalizationExample() { return ( <PageHeader dropdownAccessibilityLabel="Mehr Optionen" helperLink={{ text: 'Mehr erfahren.', accessibilityLabel: 'Erfahren Sie mehr auf Pinterest.com', href: 'http://www.pinterest.com', onClick: () => {}, }} items={[ <Datapoint key="imporessions" size="md" title="Impressionen" trend={{ value: 30, accessibilityLabel: 'Aufwärtstrend' }} value="$1.25M" />, ]} primaryAction={{ component: <Button color="red" size="lg" text="Fördern" />, dropdownItems: [ <Dropdown.Item key="fordern" onSelect={() => {}} option={{ value: 'Fördern', label: 'Fördern' }} />, ], }} secondaryAction={{ component: <Button size="lg" text="Analysen anzeigen" />, dropdownItems: [ <Dropdown.Link key="analysen" href="https://pinterest.com" option={{ value: 'Analysen anzeigen', label: 'Analysen anzeigen' }} />, ], }} subtext="5 aktive Kampagnen." title="Anzeigenübersicht" /> ); }
Variants
Title
PageHeader's title
is the main part of the component as it represents the page's main heading (it will always be a level 1 heading).
It can be complemented with three additional elements: a thumbnail (left) and a badge and/or a help Icon (right). The badge style can be changed through
type option of badge
prop.
Don't forget to localize its content.
import { Fragment, useState } from 'react'; import { Image, OverlayPanel, PageHeader, Text } from 'gestalt'; export default function PageHeaderTitleExample() { const [open, setOpen] = useState(false); return ( <Fragment> <PageHeader badge={{ text: 'New', type: 'info', tooltip: { text: 'New integration' }, }} helperIconButton={{ accessibilityControls: '', accessibilityExpanded: false, accessibilityLabel: 'Read more information about the new Pinterest integration', onClick: () => setOpen(true), }} thumbnail={ <Image alt="square" fit="cover" naturalHeight={1} naturalWidth={1} src="https://i.ibb.co/LQc8ynn/image.png" /> } title="Pinterest app" /> {open ? ( <OverlayPanel accessibilityDismissButtonLabel="Close" accessibilityLabel="Example overlay panel for demonstration" heading="Guidance" onDismiss={() => setOpen(false)} size="md" > <Text>1</Text> <Text>2</Text> <Text>3</Text> </OverlayPanel> ) : null} </Fragment> ); }
Primary action
PageHeader supports an optional primaryAction
. It can be a
Button, a
Link or an
IconButton with a
Tooltip and optional
Dropdown. Any Buttons or IconButtons should be size="lg"
.
If there's already a primary action elsewhere on the page, PageHeader can have 1 or 2 secondary actions. Use primaryAction
as an additional secondary action.
Primary and secondary actions are consolidated into
Dropdown below the
sm breakpoint. primaryAction
takes both the main component and its equivalent using Dropdown subcomponents.
For example, Button should be complemented with
Dropdown.Item, Link should be complemented with
Dropdown.Link, and an IconButton displaying a Dropdown should reuse the same Dropdown subcomponents. Don't forget to pass dropdownAccessibilityLabel
for the IconButton consolidating all actions into
Dropdown below the sm breakpoint.
Resize your window to observe how the PageHeaders below adapt to smaller screen widths.
import { Fragment, useRef, useState } from 'react'; import { Button, Divider, Dropdown, Flex, IconButton, Link, PageHeader, Text, Tooltip, } from 'gestalt'; export default function PrimaryActionExample() { const [open, setOpen] = useState(false); const [selected, setSelected] = useState([]); const anchorRef = useRef(null); const handleSelect = ({ item }) => { if (selected.some((selectedItem) => selectedItem.value === item.value)) { setSelected((localSelected) => localSelected.filter( (selectedItem) => selectedItem.value !== item.value ) ); } else { setSelected((localSelected) => [...localSelected, item]); } }; return ( <Flex direction="column" flex="grow"> <PageHeader dropdownAccessibilityLabel="Additional options" primaryAction={{ component: <Button color="red" size="lg" text="Create group" />, dropdownItems: [ <Dropdown.Item key="create-group" onSelect={() => {}} option={{ value: 'Create group', label: 'Create group' }} />, ], }} subtext="S. E. All products USD" title="Product groups" /> <Divider /> <PageHeader dropdownAccessibilityLabel="Additional options" primaryAction={{ component: ( <Fragment> <Tooltip idealDirection="up" text="Board options"> <IconButton ref={anchorRef} accessibilityControls="page-header-example" accessibilityExpanded={open} accessibilityHaspopup accessibilityLabel="Board options" icon="ellipsis" iconColor="darkGray" onClick={() => setOpen((prevVal) => !prevVal)} selected={open} size="lg" /> </Tooltip> {open && ( <Dropdown anchor={anchorRef.current} id="page-header-example" onDismiss={() => { setOpen(false); }} > <Dropdown.Item onSelect={handleSelect} option={{ value: 'Edit Board', label: 'Edit Board', }} selected={selected} /> <Dropdown.Item onSelect={handleSelect} option={{ value: 'Share', label: 'Share' }} selected={selected} /> <Dropdown.Item badge={{ text: 'New' }} onSelect={handleSelect} option={{ value: 'Merge', label: 'Merge', }} selected={selected} /> </Dropdown> )} </Fragment> ), dropdownItems: [ <Dropdown.Item key="edit-board" onSelect={() => {}} option={{ value: 'Edit board', label: 'Edit board' }} />, <Dropdown.Item key="share-board" onSelect={() => {}} option={{ value: 'Share board', label: 'Share board' }} />, <Dropdown.Item key="merge-board" onSelect={() => {}} option={{ value: 'Merge board', label: 'Merge board' }} />, ], }} title="Kitchen Reno Ideas" /> <Divider /> <PageHeader dropdownAccessibilityLabel="Additional options" primaryAction={{ component: ( <Text weight="bold"> <Link href="https://www.pinterest.com"> Switch to quick ad creation </Link> </Text> ), dropdownItems: [ <Dropdown.Link key="Visit" href="https://www.pinterest.com" option={{ value: 'Switch to quick ad creation', label: 'Switch to quick ad creation', }} />, ], }} title="Ads overview" /> </Flex> ); }
Secondary action
PageHeader also supports an optional secondaryAction
. It will likely be a
Button or an
IconButton with a
Tooltip and optional
Dropdown. Any Buttons or IconButtons should be size="lg"
.
Primary and secondary actions are consolidated into
Dropdown below the
sm breakpoint. secondaryAction
takes both the main component and its equivalent using Dropdown subcomponents.
For example, Button should be complemented with
Dropdown.Item, Link should be complemented with
Dropdown.Link, and an IconButton displaying a Dropdown should reused the same Dropdown subcomponents. Don't forget to pass dropdownAccessibilityLabel
for the IconButton consolidating all actions into
Dropdown below the sm breakpoint.
Resize your window to observe how the PageHeaders below adapt to smaller screen widths.
import { Fragment, useRef, useState } from 'react'; import { Button, Divider, Dropdown, Flex, IconButton, PageHeader, Tooltip, } from 'gestalt'; export default function SecondaryActionsExample() { const [open, setOpen] = useState(false); const [selected, setSelected] = useState([]); const anchorRef = useRef(null); const handleSelect = ({ item }) => { if (selected.some((selectedItem) => selectedItem.value === item.value)) { setSelected((selectedLocal) => selectedLocal.filter( (selectedItem) => selectedItem.value !== item.value ) ); } else { setSelected((selectedLocal) => [...selectedLocal, item]); } }; return ( <Flex direction="column" flex="grow"> <PageHeader dropdownAccessibilityLabel="Additional options" primaryAction={{ component: ( <Button color="red" size="lg" text="Create product group" /> ), dropdownItems: [ <Dropdown.Item key="create-product-group" onSelect={() => {}} option={{ value: 'Create product group', label: 'Create product group', }} />, ], }} secondaryAction={{ component: <Button size="lg" text="Promote" />, dropdownItems: [ <Dropdown.Item key="promote" onSelect={() => {}} option={{ value: 'Promote', label: 'Promote' }} />, ], }} subtext="S. E. All products USD" title="Product groups" /> <Divider /> <PageHeader dropdownAccessibilityLabel="Additional options" primaryAction={{ component: <Button color="red" size="lg" text="Create new report" />, dropdownItems: [ <Dropdown.Item key="create-new-report" onSelect={() => {}} option={{ value: 'Create new report', label: 'Create new report', }} />, ], }} secondaryAction={{ component: ( <Fragment> <Tooltip idealDirection="up" text="Board options"> <IconButton ref={anchorRef} accessibilityControls="page-header-example" accessibilityExpanded={open} accessibilityHaspopup accessibilityLabel="Board options" icon="ellipsis" iconColor="darkGray" onClick={() => setOpen((prevVal) => !prevVal)} selected={open} size="lg" /> </Tooltip> {open && ( <Dropdown anchor={anchorRef.current} id="page-header-example" onDismiss={() => { setOpen(false); }} > <Dropdown.Item key="share-report" onSelect={handleSelect} option={{ value: 'Share report', label: 'Share report', }} selected={selected} /> <Dropdown.Item onSelect={handleSelect} option={{ value: 'Help center', label: 'Help center' }} selected={selected} /> <Dropdown.Item badge={{ text: 'New' }} onSelect={handleSelect} option={{ value: 'Delete', label: 'Delete', }} selected={selected} /> </Dropdown> )} </Fragment> ), dropdownItems: [ <Dropdown.Item key="share-report" onSelect={() => {}} option={{ value: 'Share report', label: 'Share report' }} />, <Dropdown.Link key="visit-help-center" href="" iconEnd="visit" option={{ value: 'Visit help center', label: 'Visit help center', }} />, <Dropdown.Item key="delete-report" onSelect={() => {}} option={{ value: 'Delete report', label: 'Delete report' }} />, ], }} title="Custom reports" /> </Flex> ); }
Complementary items
PageHeader supports an optional pair of components next to the CTA section. It's strongly recommended to limit this space to data display components, mostly Datapoint. The complementary component section is hidden in small breakpoints.
import { Fragment, useState } from 'react'; import { Button, Datapoint, Dropdown, OverlayPanel, PageHeader, Text, } from 'gestalt'; export default function IncludeImageExample() { const [open, setOpen] = useState(false); return ( <Fragment> <PageHeader dropdownAccessibilityLabel="More options" helperIconButton={{ accessibilityControls: '', accessibilityExpanded: false, accessibilityLabel: 'Read more information about Ads overview', onClick: () => setOpen(true), }} items={[ <Datapoint key="impressions" size="md" title="Impressions" trend={{ value: 30, accessibilityLabel: 'Trending up' }} value="$1.25M" />, <Datapoint key="engagement" size="md" title="Engagement" trend={{ value: 5, accessibilityLabel: 'Trending up' }} value="10%" />, ]} primaryAction={{ component: <Button color="red" size="lg" text="Promote" />, dropdownItems: [ <Dropdown.Item key="promote" onSelect={() => {}} option={{ value: 'Promote', label: 'Promote' }} />, ], }} secondaryAction={{ component: <Button size="lg" text="View analytics" />, dropdownItems: [ <Dropdown.Link key="view-analytics" href="https://pinterest.com" option={{ value: 'View analytics', label: 'View analytics' }} />, ], }} title="Ads overview" /> {open ? ( <OverlayPanel accessibilityDismissButtonLabel="Close" accessibilityLabel="Example overlay panel for demonstration" heading="Guidance" onDismiss={() => setOpen(false)} size="md" > <Text>1</Text> <Text>2</Text> <Text>3</Text> </OverlayPanel> ) : null} </Fragment> ); }
Subtext
subtext
should be used to add metadata about the content on the page, not to describe the page itself. They can be complemented with a helperLink
.
import { Button, Dropdown, PageHeader } from 'gestalt'; export default function IncludeImageExample() { return ( <PageHeader dropdownAccessibilityLabel="Additional options" helperLink={{ accessibilityLabel: '', text: 'Learn more about bulk product catalog uploads.', href: 'http://www.pinterest.com', onClick: () => {}, }} primaryAction={{ component: <Button color="red" size="lg" text="Quick create" />, dropdownItems: [ <Dropdown.Item key="quick-create" onSelect={() => {}} option={{ value: 'Quick create', label: 'Quick create' }} />, ], }} subtext="2,131 catalog products." title="Create product group" /> ); }
Max width & border
A maxWidth
should only be supplied when the content of the page is center aligned. The PageHeader’s padding should match the page’s overall padding.
PageHeader also supports a bottom border to show the division between PageHeader and the page content below.
import { Box, Dropdown, Flex, Heading, IconButton, PageHeader, SelectList, TextField, Tooltip, } from 'gestalt'; export default function PageHeaderCenterExample() { return ( <Box color="secondary" height="100%" width="100%"> <PageHeader borderStyle="sm" dropdownAccessibilityLabel="Additional options" maxWidth="65%" primaryAction={{ component: ( <Tooltip idealDirection="up" text="Additional options"> <IconButton accessibilityLabel="Additional options" icon="ellipsis" iconColor="darkGray" size="lg" /> </Tooltip> ), dropdownItems: [ <Dropdown.Item key="item" onSelect={() => {}} option={{ value: 'Item', label: 'Item' }} />, ], }} title="Settings" /> <Flex justifyContent="center"> <Box paddingY={6} width="60%"> <Flex direction="column" gap={{ row: 0, column: 5, }} > <Heading accessibilityLevel={2} size="400"> Edit profile </Heading> <TextField id="b-textfield1" label="Name" onChange={() => {}} placeholder="Placeholder" /> <Flex gap={{ row: 2, column: 0, }} > <Flex.Item flex="grow"> <TextField id="b-textfield2" label="Phone" onChange={() => {}} placeholder="Placeholder" /> </Flex.Item> <Flex.Item flex="grow"> <TextField id="b-textfield3" label="Email" onChange={() => {}} placeholder="Placeholder" /> </Flex.Item> </Flex> <SelectList id="selectlist1" label="Location" onChange={() => {}} placeholder="Placeholder" > {[ { value: 'belgium', label: 'Belgium' }, { value: 'france', label: 'France' }, { value: 'usa', label: 'USA' }, ].map(({ label, value }) => ( <SelectList.Option key={label} label={label} value={value} /> ))} </SelectList> </Flex> </Box> </Flex> </Box> ); }
Responsive
PageHeader is responsive to different viewport breakpoints.
Therefore, PageHeader’s behavior relies on the window size and requires PageHeader to be used on a full-window width to correctly respond to different breakpoints. Don’t use PageHeader right next to elements such as side-navigation bars that wouldn’t allow PageHeader to extend the full width of the window.
PageHeader doesn't depend on DeviceTypeProvider to display a mobile view; instead, it adjusts to the smallest viewport breakpoint. The example below forces a mobile viewport width to render Pageheader at that particular viewport.
import { Fragment, useRef, useState } from 'react'; import { Button, Dropdown, IconButton, PageHeader, Tooltip } from 'gestalt'; export default function SecondaryActionsExample() { const [open, setOpen] = useState(false); const [selected, setSelected] = useState([]); const anchorRef = useRef(null); const handleSelect = ({ item }) => { if (selected.some((selectedItem) => selectedItem.value === item.value)) { setSelected((selectedLocal) => selectedLocal.filter( (selectedItem) => selectedItem.value !== item.value ) ); } else { setSelected((selectedLocal) => [...selectedLocal, item]); } }; return ( <PageHeader dropdownAccessibilityLabel="Additional options" primaryAction={{ component: <Button color="red" size="lg" text="Create new report" />, dropdownItems: [ <Dropdown.Item key="create-new-report" onSelect={() => {}} option={{ value: 'Create new report', label: 'Create new report' }} />, ], }} secondaryAction={{ component: ( <Fragment> <Tooltip idealDirection="up" text="Board options"> <IconButton ref={anchorRef} accessibilityControls="page-header-example" accessibilityExpanded={open} accessibilityHaspopup accessibilityLabel="Board options" icon="ellipsis" iconColor="darkGray" onClick={() => setOpen((prevVal) => !prevVal)} selected={open} size="lg" /> </Tooltip> {open && ( <Dropdown anchor={anchorRef.current} id="page-header-example" onDismiss={() => { setOpen(false); }} > <Dropdown.Item key="share-report" onSelect={handleSelect} option={{ value: 'Share report', label: 'Share report', }} selected={selected} /> <Dropdown.Item onSelect={handleSelect} option={{ value: 'Help center', label: 'Help center' }} selected={selected} /> <Dropdown.Item badge={{ text: 'New' }} onSelect={handleSelect} option={{ value: 'Delete', label: 'Delete', }} selected={selected} /> </Dropdown> )} </Fragment> ), dropdownItems: [ <Dropdown.Item key="share-report" onSelect={() => {}} option={{ value: 'Share report', label: 'Share report' }} />, <Dropdown.Link key="visit-help-center" href="" iconEnd="visit" option={{ value: 'Visit help center', label: 'Visit help center' }} />, <Dropdown.Item key="delete-report" onSelect={() => {}} option={{ value: 'Delete report', label: 'Delete report' }} />, ], }} title="Custom reports" /> ); }
Writing
- Use sentences for titles capitalizing proper names and product names, including the word “Pin”
- Make sure page titles match the menu item that was used to navigate to the page
- Keep subtext short to account for localization and smaller screens
- Make page titles, subtext and action text lengthy so that it truncates quickly at smaller screen sizes
Component quality checklist
Quality item | Status | Status description |
---|---|---|
Figma Library | Ready | Component is available in Figma for web and mobile web. |
Responsive Web | Ready | Component responds to changing viewport sizes in web and mobile web. |
Internal documentation
Related
Heading
Heading allows you to show headings on the page, therefore, it should be used to create level 2-6 headings on a page. If a level 1 heading is needed, use PageHeader. Use as a title for sections below PageHeader, or for when a page needs a title but doesn’t warrant a PageHeader.