Overview

Most pages in the application have a repeatable pattern of components. This documentation provides the general best practice for what can be added to a header, where in it, and when to use it. This pattern is used in conjunction with our Page component.

Principles

  1. Consistency in structure and page relation. On any given page, how the header arranges descriptions, actions, and navigation should be similar across the application.

  2. Prioritize small vertical height. Many pages in the ServiceTitan app are dense. Headers should help users complete their tasks on a page, but should get out of the way for the page’s main content.

  3. Consistency in foundational styles. There are many ways to subtly visualize the same header. Standardizing the foundational choices, from text sizes to button variations, improves familiarity to users.

Header Anatomy

Page Headers are broken up into three general functions: The description of the page, actions on the page, and navigation within the page.

scaled: true
---
const Example = () => {
    const breadcrumbs = (
        <Breadcrumb className="m-b-1">
            <Breadcrumb.Link label="Root page" />
            <Breadcrumb.Link label="Subpage" />
            <Breadcrumb.Link label="Current page" />
        </Breadcrumb>
    );
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
            <Tag>Tag</Tag>
        </Stack>
    );
    const actions = (
        <ButtonGroup>
            <Button small>Action</Button>
            <Button primary small>Primary Action</Button>
        </ButtonGroup>
    );
    const description = (
        <BodyText subdued className="m-t-1">A standard description for the entire page.</BodyText>
    );

    return (
        <Frame
            /* Frame's header prop is a visual demo. */
            header={<div style={{ height: 56 }}><div style={{backgroundColor: "#141414",height: 56,position: "fixed",top: 0,width: "100%",zIndex: 1}}/></div>}
        >
            <Page
                header={
                    <>
                        {breadcrumbs}
                        <Stack alignItems="center" spacing={2} wrap='wrap'>
                            <Stack.Item fill>
                                {title('Page Title')}
                            </Stack.Item>
                            {actions}
                        </Stack>
                        {description}
                        <TabGroup className="m-t-3">
                            <Tab active>Active Tab</Tab>
                            <Tab>Action Required</Tab>
                            <Tab>Insights</Tab>
                            <Tab>Settings</Tab>
                        </TabGroup>
                    </>
                }
            >
                <Banner
                    icon
                    title="You are viewing an inactive item."
                    className="m-b-4"
                />
                Content of the page.
            </Page>
        </Frame>
    );
}
render (<Example />)

Metadata

Most pages will provide a basic amount of information in the header, typically just a page title.

Page title

Most pages should have a title. The title should represent the main content of the page itself, including user-generated titles.

const Example = () => {
    const title = (text = 'Page Title') => <Headline size="large" className="m-b-0">{text}</Headline>;

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Page Title')}
                        </Stack.Item>
                    </Stack>
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            Content of the page.
        </Page>
    );
}
render (<Example />)

Description

Descriptions provide a more detailed overview of what the page is about. In general this will be a single line of text, but can be customized to fit the specific needs of a page. A title should always be used with a description.

const Example = () => {
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
        </Stack>
    );
    const description = (
        <BodyText subdued className="m-t-1">Edit requisitions in the table before you confirm and save.</BodyText>
    );

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Requisition #1234567')}
                        </Stack.Item>
                    </Stack>
                    {description}
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            Content of the page.
        </Page>
    );
}
render (<Example />)
const Example = () => {
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
        </Stack>
    );
    const description = (
        <Grid columns={3} className="m-t-3">
            <Grid.Column>
                <Eyebrow >Category</Eyebrow>
                <Headline className='m-t-half m-b-0 t-truncate' size='small'>Referrals – Widgets</Headline>
            </Grid.Column>
            <Grid.Column>
                <Eyebrow>Business Unit</Eyebrow>
                <Headline className='m-t-half m-b-0 t-truncate' size='small'>Change-Out</Headline>
            </Grid.Column>
            <Grid.Column>
                <Eyebrow>Advertised Number</Eyebrow>
                <Headline className='m-t-half m-b-0 t-truncate' size='small'>(123) 456–7890</Headline>
            </Grid.Column>
        </Grid>
    );

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Campaign Promo')}
                        </Stack.Item>
                    </Stack>
                    {description}
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            Content of the page.
        </Page>
    );
}
render (<Example />)

Tags

Tags provide useful metadata about the page. The relative importance of this data varies from page to page: the visual variations provided by Tags can help prioritize them in the header.

const Example = () => {
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
            <Tag color='info' compact>Scheduled</Tag>
        </Stack>
    );

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Batch #33: Plumbing Small Parts')}
                        </Stack.Item>
                    </Stack>
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            Content of the page.
        </Page>
    );
}
render (<Example />)
const Example = () => {
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
        </Stack>
    );
    const description = (
        <div>
            <TagGroup className="m-t-1 m-b-2">
                <Tag color='success'>Active</Tag>
                <Tag subtle>Unsold Estimates</Tag>
            </TagGroup>
            <BodyText subdued>A standard description for the entire page.</BodyText>
        </div>
    );

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Old Equipment')}
                        </Stack.Item>
                    </Stack>
                    {description}
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            Content of the page.
        </Page>
    );
}
render (<Example />)

Actions

Actions can refer to many different activities a user can perform at the page level.

Types of Actions

The majority of page-level actions should be located within the header. Some actions are more appropriate for the page footer. Actions that only apply to a section of the page, or tied to a Table, should

  • Add / Create
  • Starting a Flow
  • Edit page contents
  • Switch to
  • Secondary page actions, such as download, share, print, import, etc.
  • A generic action that brings the user to a new page, Takeover, or Modal.
  • Search and filtering

Actions that should be avoided in the page header

The majority of these actions are located at the bottom of the page.

  • Save
  • Canceling a process
  • Previous / Next functionality
  • Ending / Finalizing a flow
  • Actions that are explicitly tied to a Form

Button-based Actions

Most actions can be represented through Buttons. These appear on the right side of the page. Except for the overflow menu, the most important actions are on the right-most side of the header. There should only be between 0-1 primary actions (visually a solid blue button) in the page header. Button actions should not be explicitly tied to a Table.

---
const Example = () => {
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
        </Stack>
    );
    const actions = (
        <ButtonGroup>
            <Button small>View on Mobile</Button>
            <Button small primary>Create Rule</Button>
        </ButtonGroup>
    );

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Markups')}
                        </Stack.Item>
                        {actions}
                    </Stack>
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            Content of the page.
        </Page>
    );
}
render (<Example />)
---
const Example = () => {
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
        </Stack>
    );
    const actions = (
        <ButtonGroup>
            <Tooltip text='Print invoice'><Button small iconName='print' /></Tooltip>
            <Tooltip text='Email invoice'><Button small iconName='email' /></Tooltip>
            <Tooltip text='Edit invoice' direction='tl'><Button small iconName='edit' /></Tooltip>
        </ButtonGroup>
    );

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Invoice #123456')}
                        </Stack.Item>
                        {actions}
                    </Stack>
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            Content of the page.
        </Page>
    );
}
render (<Example />)

Overflow Action Menu

An overflow menu is placed on the right-most side of the header. This menu is useful when there are many page-level actions, the number of potential overflow actions varies by context, and there is a need to purposefully deemphasize actions (e.g. delete). The disadvantage of an overflow menu is the lack of discoverability of the actions and the extra clicks it takes to complete an action.

---

const Example = () => {
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
        </Stack>
    );
    const [open, setOpen] = React.useState(false);
    const actions = (
        <ButtonGroup>
            <Button small>Clone</Button>

            <ActionMenu
                open={open}
                trigger={<Button small iconName='more_vert' onClick={() => setOpen(!open)} />}
                direction="bl"
            >
                <ActionMenu.Item>Edit</ActionMenu.Item>
                <ActionMenu.Item>Delete</ActionMenu.Item>
            </ActionMenu>

        </ButtonGroup>
    );

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Campaign: New Style HVAC')}
                        </Stack.Item>
                        {actions}
                    </Stack>
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            Content of the page.
        </Page>
    );
}
render (<Example />)

Search and Filtering

Page-level search and filtering are separated from Button-based actions. When they manipulate data on the page, they appear below the description area, starting on the left. When they manipulate data across many pages, they are to the left of other persistent Button-based actions.

---
const Example = () => {
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
        </Stack>
    );
    const actions = <Button small>Clone</Button>;

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Page Title')}
                        </Stack.Item>
                        {actions}
                    </Stack>
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            <Form className="m-b-4">
                <Form.Input placeholder='Search Content' icon="search" iconPosition="left" small style={{ width: '300px' }} />
            </Form>
            <BodyText>Rest of Page Content</BodyText>
        </Page>
    );
}
render (<Example />)
---
const Example = () => {
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
        </Stack>
    );
    const actions = <Button small>Clone</Button>;

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Page Title')}
                        </Stack.Item>
                        {actions}
                    </Stack>
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            <Form className="m-b-4">
                <Form.Group>
                    <Form.AnvilSelect label="Date Type" icon="search" iconPosition="left" trigger={{ size: 'small', placeholder: 'Completion Date' }} options={[]} />
                    <Form.AnvilSelect label="Date Range" icon="search" iconPosition="left" trigger={{ size: 'small', placeholder: 'Last 30 Days' }} options={[]} />
                    <Form.AnvilSelect label="Business Unit" shortLabel="funnel" trigger={{ size: 'small', placeholder: 'Business Unit' }} options={[]} />
                </Form.Group>
            </Form>
            <BodyText>Rest of Page Content</BodyText>
        </Page>
    );
}
render (<Example />)

Breadcrumbs are an optional, supplemental navigational element that helps users understand where they are in the app. Breadcrumbs don’t show where a user has been, but shows the underlying site structure to a user. If a page can be accessed through multiple parent pages (i.e. poly-hierarchical), a single pathway should be designated. Assuming a page title exists, the page you are on should not be represented in the breadcrumbs.

const Example = () => {
    const breadcrumbs = (
        <Breadcrumb className="m-b-1">
            <Breadcrumb.Link label="Root page" />
            <Breadcrumb.Link label="Subpage" />
            <Breadcrumb.Link label="Current page" />
        </Breadcrumb>
    );
    const title = (text = 'Page Title') => <Headline size="large" className="m-b-0">{text}</Headline>;
    const actions = <Button small>Action</Button>;

    return (
        <Page
            header={
                <>
                    {breadcrumbs}
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Page Title')}
                        </Stack.Item>
                        {actions}
                    </Stack>
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            Content of the page.
        </Page>
    );
}
render (<Example />)

When to use Breadcrumbs

  • If the page has a parent page (i.e. a page represented in the sidebar or global header)
  • When a user has likely entered the page from outside the hierarchy

When not to use Breadcrumbs

  • When there is no parent page to return to, such as if the page appears in a Sidebar.
  • Within a flow or wizard. The Back Link can be used instead.
  • To replace any other navigational structure.

Tertiary navigation via Tabs

A tertiary navigation (primary navigation being the global header, secondary being the sidebar) can be added to the Page header. This is represented through the Tab component. Tabs in the header can be divided into two purposes: to navigate to different pages related to the title, and to filter content on the page, such as a table.

Page-level Tabs

Use page-level Tabs when the header content above the Tabs does not change between tab switches. Types of content below the page typically varies between tabs.

const Example = () => {
    const title = (text = 'Page Title') => <Headline size="large" className="m-b-0">{text}</Headline>;

    return (
        <Page
            header={
                <>
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Sync Log')}
                        </Stack.Item>
                    </Stack>
                    <TabGroup className="m-t-3">
                        <Tab active>Services</Tab>
                        <Tab>Material</Tab>
                        <Tab>Equipment</Tab>
                    </TabGroup>
                </>
            }
        >
            Content of the page.
        </Page>
    );
}
render (<Example />)

Notifications

Banner notifications should appear at the bottom of the page header, above the start of the page content.

const Example = () => {
    const breadcrumbs = (
        <Breadcrumb className="m-b-1">
            <Breadcrumb.Link label="Root page" />
            <Breadcrumb.Link label="Subpage" />
            <Breadcrumb.Link label="Current page" />
        </Breadcrumb>
    );
    const title = (text = 'Page Title') => (
        <Stack alignItems="center" spacing={1}>
            <Headline size="large" className="m-b-0">{text}</Headline>
            <Tag>Tag</Tag>
        </Stack>
    );
    const actions = (
        <ButtonGroup>
            <Button small>Action</Button>
            <Button primary small>Primary Action</Button>
        </ButtonGroup>
    );
    const description = (
        <BodyText subdued className="m-t-1">A standard description for the entire page.</BodyText>
    );

    return (
        <Page
            header={
                <>
                    {breadcrumbs}
                    <Stack alignItems="center" spacing={2} wrap='wrap'>
                        <Stack.Item fill>
                            {title('Page Title')}
                        </Stack.Item>
                        {actions}
                    </Stack>
                    {description}
                    <div className="m-t-4" style={{ background: '#DFE0E1', height: '1px'}} />
                </>
            }
        >
            <Banner
                icon
                title="You are viewing an inactive item."
                className="m-b-4"
            />
            Content of the page.
        </Page>
    );
}
render (<Example />)

Boxed Style Headers

The header can be customized to be "boxed" from the content below it. It applies a full-width white background. This styling can be used when incorporating a custom fixed header style, or when the page is very dense in content. When using, it is recommended to consistently use it across the same section of the app. Caution should be used when using this style, as it is visually heavier, and is a little more difficult to technically maintain.

scaled: true
---
const Example = () => {
    const breadcrumbs = (
        <Breadcrumb className="m-b-1">
            <Breadcrumb.Link label="Root page" />
            <Breadcrumb.Link label="Subpage" />
            <Breadcrumb.Link label="Current page" />
        </Breadcrumb>
    );
    const title = (text = 'Page Title') => <Headline size="large" className="m-b-0">{text}</Headline>;
    const actions = <Button primary small>Primary Action</Button>;
    const description = <BodyText subdued className="m-t-1 m-b-3">A standard description for the entire page.</BodyText>;

    return (
        <Frame
            /* Frame's header prop is a visual demo. */
            header={<div style={{ height: 56 }}><div style={{backgroundColor: "#141414",height: 56,position: "fixed",top: 0,width: "100%",zIndex: 1}}/></div>}
        >
            <Page
                header={
                    <div className="bg-white p-t-3" style={{ borderBottom: '1px solid #dfe0e1' }}>
                        <div className="m-x-auto p-x-5" style={{ maxWidth: '1280px' }}>
                            {breadcrumbs}
                            <Stack alignItems="center" spacing={2} wrap='wrap'>
                                <Stack.Item fill>
                                    {title('Page Title')}
                                </Stack.Item>
                                {actions}
                            </Stack>
                            {description}
                        </div>
                    </div>
                }
                sidebar={
                    <Sidebar>
                    </Sidebar>
                }
                spacing='none'
                maxWidth='wide'
            >
                <div className="m-x-auto p-b-3 p-x-5 p-y-2" style={{ maxWidth: '1280px' }}>
                    Content of the page.
                </div>
            </Page>
        </Frame>
    );
}
render (<Example />)

Example with Tabs

The box styling can be paired well with Tabs. This use case works well when Tabs act as navigation between different types of page content.

const Example = () => {
    const title = (text = 'Page Title') => <Headline size="large" className="m-b-0">{text}</Headline>;

    return (
        <Page
            header={
                <div className="bg-white p-t-3" style={{ borderBottom: '1px solid #dfe0e1' }}>
                    <div className="m-x-auto p-x-5" style={{ maxWidth: '1280px' }}>
                        <Stack alignItems="center" spacing={2} wrap='wrap'>
                            <Stack.Item fill>
                                {title('Page Title')}
                            </Stack.Item>
                        </Stack>
                        <TabGroup className="m-t-3" divider={false}>
                            <Tab active>Ready To Bill <Tag compact badge>127</Tag></Tab>
                            <Tab>Action Required <Tag compact badge color="critical">25</Tag></Tab>
                            <Tab>Insights</Tab>
                            <Tab>Settings</Tab>
                        </TabGroup>
                    </div>
                </div>
            }
            spacing='none'
            maxWidth='wide'
        >
            <div className="m-x-auto p-b-3 p-x-5" style={{ maxWidth: '1280px' }}>
                Content of the page.
            </div>
        </Page>
    );
}
render (<Example />)

Best Practices

  • Almost all pages should at least have a title.
  • Page Headers should be paired with Page Layout.