<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
>
Content of the page.
</Page>
Width Properties
By default, pages have a max width of 1280px
. This can be shrunk to 768px
, or removed altogether.
<div>
<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Thin Max Width, Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
maxWidth="narrow"
>
<Card><Card.Section red>Content that is apart of the page.</Card.Section></Card>
</Page>
<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Default Max Width, Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
>
<Card><Card.Section red>Content that is apart of the page.</Card.Section></Card>
</Page>
<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">No Max Width, Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
maxWidth="wide"
>
<Card><Card.Section red>Content that is apart of the page.</Card.Section></Card>
</Page>
</div>
Forcing a Minimum Width
By default, pages do not have a min width. A min width can be enabled on the Page's content to prevent the underlying components from being responsive, creating a horizontal scroll effect. The minimum width value is set to 1280px
for default and wide Pages, and 768px
for narrow Pages. This can be useful when page content is not useful to scale down, such as tables.
<div>
<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Thin Max Width, Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
maxWidth="narrow"
minWidth
>
<Card><Card.Section red>Content that is apart of the page.</Card.Section></Card>
</Page>
<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Default Max Width, Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
minWidth
>
<Card><Card.Section red>Content that is apart of the page.</Card.Section></Card>
</Page>
<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">No Max Width, Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
maxWidth="wide"
minWidth
>
<Card><Card.Section red>Content that is apart of the page.</Card.Section></Card>
</Page>
</div>
Margins
The spacing between the Page content and the rest of the UI can be removed. This is useful when designing for page background stylings (colors, separators).
<div>
<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Relaxed Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
>
<Card><Card.Section red>Content that is apart of the page.</Card.Section></Card>
</Page>
<br/>
<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">No Spacing Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
spacing="none"
>
<Card><Card.Section red>Content that is apart of the page.</Card.Section></Card>
</Page>
</div>
Left Aligned
The content area of a page can also be left aligned.
<div>
<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
align="left"
>
<Card><Card.Section red>Content that is apart of the page.</Card.Section></Card>
</Page>
</div>
Background Color
The background color for the page can be set either to neutral-10 (#FCFCFC
, the default) or White/neutral-0 (#FFFFFF
).
<Page
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Page Title on a White Background</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
backgroundColor="neutral-0"
>
Content of the page.
</Page>
The page header is where the top content of a page is contained. It currently accepts most forms of content. For more indepth use cases, see the Page Header pattern.
The page footer is where the save and cancel action is contained for page level submission.
<State>
{([open, setOpen]) => (
<Page
style={{ height: '100%' }}
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
</Stack>
}
footer={
<Stack
className="w-100"
justifyContent="space-between"
direction="row-reverse"
>
<ButtonGroup className="flex-row-reverse">
<ButtonGroup attached>
<Button primary>Save</Button>
<Popover
trigger={
<Button
primary
iconName={`expand_${open ? `less` : `more`}`}
onClick={() => setOpen(!open)}
/>
}
onClickOutside={() => setOpen(false)}
width="xs"
padding={null}
open={open}
direction="tl"
>
<OptionList
options={[{ value: 1, text: 'Save as...' }]}
/>
</Popover>
</ButtonGroup>
<Button>Reset</Button>
</ButtonGroup>
</Stack>
}
>
Content of the page.
</Page>
)}
</State>
The page can include a Sidebar on the left side with consistent sizing. See the Sidebar docs for more information on using Sidebars.
<State initial={0}>
{([active, setActive]) => (
<Page
sidebar={
<Sidebar localStorageKey="page__sidebar">
<Sidebar.Section>
<SideNav title="Marketing">
<SideNav.Item onClick={() => setActive(0)} active={active===0}>Dashboard</SideNav.Item>
<SideNav.Item onClick={() => setActive(1)} active={active===1}>Campaigns</SideNav.Item>
<SideNav.Item onClick={() => setActive(2)} active={active===2}>Emails</SideNav.Item>
<SideNav.Item onClick={() => setActive(3)} active={active===3}>Audiences</SideNav.Item>
</SideNav>
</Sidebar.Section>
<Sidebar.Section>
<Card onClick={() => setActive(4)} active={active===4}>Test</Card>
</Sidebar.Section>
</Sidebar>
}
>
<Layout type="2Col">
<Layout.Section>
<Card>Card 1</Card>
</Layout.Section>
<Layout.Section>
<Card>Card 2</Card>
</Layout.Section>
</Layout>
</Page>
)}
</State>
Placed above the page header and content, the Action Toolbar provides a location for the Actions and Inputs components to filter, change, or update content and to take action on selected items.
Do not use this to show page title or status -- use
Page Header instead.
<State initial={0}>
{([active, setActive]) => (
<Page
actionToolbar={{
content: (
<Stack style={{ flex: 1 }} justifyContent="space-between">
<Stack.Item>
<ButtonToggle
small
options={[
{
text: "Daily",
selected: true,
},
{
text: "Weekly",
},
{
text: "Routing",
},
]}
/>
</Stack.Item>
<Stack.Item>
<Stack>
<Tooltip text="Schedules">
<Button fill="subtle" iconName="watch_later" small />
</Tooltip>
<Divider vertical spacing={2} />
<ButtonGroup>
<Tooltip text="Notifications">
<Button fill="subtle" iconName="notifications" small />
</Tooltip>
<Tooltip text="Refresh">
<Button fill="subtle" iconName="autorenew" small />
</Tooltip>
<Tooltip text="Settings">
<Button fill="subtle" iconName="settings" small />
</Tooltip>
<Tooltip text="Messages">
<Button fill="subtle" iconName="email" small />
</Tooltip>
</ButtonGroup>
</Stack>
</Stack.Item>
</Stack>
)
}}
sidebar={
<Sidebar>
<Sidebar.Section>
<SideNav title="Marketing">
<SideNav.Item onClick={() => setActive(0)} active={active===0}>Dashboard</SideNav.Item>
<SideNav.Item onClick={() => setActive(1)} active={active===1}>Campaigns</SideNav.Item>
<SideNav.Item onClick={() => setActive(2)} active={active===2}>Emails</SideNav.Item>
<SideNav.Item onClick={() => setActive(3)} active={active===3}>Audiences</SideNav.Item>
</SideNav>
</Sidebar.Section>
<Sidebar.Section>
<Card onClick={() => setActive(4)} active={active===4}>Test</Card>
</Sidebar.Section>
</Sidebar>
}
>
<Layout type="2Col">
<Layout.Section>
<Card>Card 1</Card>
</Layout.Section>
<Layout.Section>
<Card>Card 2</Card>
</Layout.Section>
</Layout>
</Page>
)}
</State>
When to not use this
Do not use this with Save
action. We have established that pattern for saving content or form to be on the footer of the page.
Design considerations
To preserve visual hierarchy and to minimize vertical space used, use smaller size components, like Small Buttons, 24px Icons, Medium BodyText
Sticky position
Use a sticky positioning when the page length is longer than 2 viewport height, requiring users to scroll multiple times to view the content they need.
<State initial={0}>
{([active, setActive]) => (
<Page
style={{ height: '100%' }}
actionToolbar={{
sticky: true,
content: (
<Stack style={{ flex: 1 }} justifyContent="space-between">
<Stack.Item>
<ButtonToggle
small
options={[
{
text: "Daily",
selected: true,
},
{
text: "Weekly",
},
{
text: "Routing",
},
]}
/>
</Stack.Item>
<Stack.Item>
<Stack>
<Tooltip text="Schedules">
<Button fill="subtle" iconName="watch_later" small />
</Tooltip>
<Divider vertical spacing={2} />
<ButtonGroup>
<Tooltip text="Notifications">
<Button fill="subtle" iconName="notifications" small />
</Tooltip>
<Tooltip text="Refresh">
<Button fill="subtle" iconName="autorenew" small />
</Tooltip>
<Tooltip text="Settings">
<Button fill="subtle" iconName="settings" small />
</Tooltip>
<Tooltip text="Messages">
<Button fill="subtle" iconName="email" small />
</Tooltip>
</ButtonGroup>
</Stack>
</Stack.Item>
</Stack>
)
}}
sidebar={
<Sidebar>
<Sidebar.Section>
<SideNav title="Marketing">
<SideNav.Item onClick={() => setActive(0)} active={active===0}>Dashboard</SideNav.Item>
<SideNav.Item onClick={() => setActive(1)} active={active===1}>Campaigns</SideNav.Item>
<SideNav.Item onClick={() => setActive(2)} active={active===2}>Emails</SideNav.Item>
<SideNav.Item onClick={() => setActive(3)} active={active===3}>Audiences</SideNav.Item>
</SideNav>
</Sidebar.Section>
<Sidebar.Section>
<Card onClick={() => setActive(4)} active={active===4}>Test</Card>
</Sidebar.Section>
</Sidebar>
}
>
<Layout type="3Col">
{[...Array.from(Array(30).keys())].map((e, i) => (
<Layout.Section key={e}>
<Card>
<Card.Section style={{ height: 100 }}>
Card {i}
</Card.Section>
</Card>
</Layout.Section>
))}
</Layout>
</Page>
)}
</State>
Responsiveness
When horizontal space is limited and the content of the Action Toolbar is overflowing, create a responsive design utilizing components such as an Action Menu to avoid horizontal scrolling and do not wrap it to next line.
Behavior
This example does not include responsiveness.
<State initial={true}>
{([open, setOpen]) => (
<Page
actionToolbar={{
sticky: true,
content: (
<Stack style={{ flex: 1 }} justifyContent="space-between">
<Stack.Item>
<ButtonToggle
small
options={[
{
text: "Daily",
selected: true,
},
{
text: "Weekly",
},
{
text: "Routing",
},
]}
/>
</Stack.Item>
<Stack.Item>
<Stack>
<Tooltip text="Schedules">
<Button fill="subtle" iconName="watch_later" small />
</Tooltip>
<Divider vertical spacing={2} />
<Button fill="subtle" iconName="notifications" small />
<ActionMenu
direction="bl"
width="xs"
trigger={
<Button
fill="subtle"
iconName="more_vert"
small
onClick={() => setOpen(!open)}
/>
}
open={open}
portal={true}
>
<ActionMenu.Item>Refresh</ActionMenu.Item>
<ActionMenu.Item>Settings</ActionMenu.Item>
<ActionMenu.Item>Messages</ActionMenu.Item>
</ActionMenu>
</Stack>
</Stack.Item>
</Stack>
)
}}
/>
)}
</State>
Panel
The Panel on a Page displays supplementary content of the Page or select item in the Page.
Position
Right Position
<Page
style={{ height: '100vh' }}
panel={{
content: (
<div>
<Stack alignItems="center" justifyContent="space-between">
<Headline size="small" className="m-0">Page Info</Headline>
</Stack>
<BodyText>Supplementary content of the Page here...</BodyText>
</div>
)
}}
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
>
<Layout type="2Col">
<Layout.Section>
<Card>Card 1</Card>
</Layout.Section>
<Layout.Section>
<Card>Card 2</Card>
</Layout.Section>
</Layout>
</Page>
Left Position
Do not use left positioned Panel when Sidebar is present. This can create confusion that the Panel content is part of the navigation.
<Page
style={{ height: '100vh' }}
panel={{
position: 'left',
content: (
<div>
<Stack alignItems="center" justifyContent="space-between">
<Headline size="small" className="m-0">Page Info</Headline>
</Stack>
<BodyText>Supplementary content of the Page here...</BodyText>
</div>
)
}}
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
>
<Layout type="2Col">
<Layout.Section>
<Card>Card 1</Card>
</Layout.Section>
<Layout.Section>
<Card>Card 2</Card>
</Layout.Section>
</Layout>
</Page>
Hide & Reveal
A Panel also has ability to hide and reveal itself. This is used to bring up supplementary content of a selected item on a page.
<State initial={0}>
{([open, setOpen]) => (
<Page
style={{ height: '100vh' }}
panel={{
open: open,
content: (
<div>
<Stack alignItems="center" justifyContent="space-between">
<Headline size="small" className="m-0">Card Detail</Headline>
<Button size="small" fill="subtle" iconName="close" onClick={() => setOpen(false)} />
</Stack>
<BodyText>Supplementary content of the Card here...</BodyText>
</div>
)
}}
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
>
<Layout>
<Layout.Section>
<Stack spacing={1} direction="column">
<Card>
<Stack alignItems="center" justifyContent="space-between">
Card 1
<Button onClick={() => setOpen(true)}>show detail</Button>
</Stack>
</Card>
<Card>
<Stack alignItems="center" justifyContent="space-between">
Card 2
<Button onClick={() => setOpen(true)}>show detail</Button>
</Stack>
</Card>
</Stack>
</Layout.Section>
</Layout>
</Page>
)}
</State>
No Padding
<Page
style={{ height: '100vh' }}
panel={{
noPadding: true,
content: (
<div>
<Stack alignItems="center" justifyContent="space-between">
<Headline size="small" className="m-0">Page Info</Headline>
</Stack>
<BodyText>Supplementary content of the Page here...</BodyText>
</div>
)
}}
header={
<Stack alignItems="center" spacing="0">
<Stack.Item fill>
<Headline size="large">Page Title</Headline>
<BodyText subdued>A short page description.</BodyText>
</Stack.Item>
<Button primary>Action</Button>
</Stack>
}
>
<Layout type="2Col">
<Layout.Section>
<Card>Card 1</Card>
</Layout.Section>
<Layout.Section>
<Card>Card 2</Card>
</Layout.Section>
</Layout>
</Page>
Best Practices
- Pages should contain at least the title of the page.
Importing
import { Page } from '@servicetitan/design-system';