> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Page - Web

> The foundation for creating page layouts in Anvil2.

> [!NOTE]
>
> For development, import `Page` from
> `@servicetitan/anvil2`. For design, find it in the
> [Anvil2 file](https://www.figma.com/design/xaNQsvRKczoxu7pj0M6QT8/Anvil2-v3?node-id=2847-3098).

![](https://mintcdn.com/servicetitan/uz2PQSvO75TRhQ38/images/docs/web/components/shared/overview-of-a-page-component.png?fit=max\&auto=format\&n=uz2PQSvO75TRhQ38\&q=85\&s=c2cd1bd6b8988636a9f9b80aa7704599)

## Anatomy

The Page consists of five primary elements that work together to create page layouts in Anvil2.

![Page
Annotation](https://mintlify.s3.us-west-1.amazonaws.com/servicetitan/images/docs/web/components/page/web/page-annotation.png)

1. **Header** - page title, and the optional description, breadcrumbs, status chips, and page actions
2. **Main content** - primary page body, aligned with the header
3. **Footer** (Optional) - sticky bar at the bottom of the main content for actions that stay in reach
4. **Panel** (Optional) - supporting content beside the page, or stacked under it on smaller screens
5. **Sidebar** (Optional) - in-page navigation that collapses on wide screens and becomes a menu on smaller screens

## Options

The Page supports the following configurations to accommodate various page layout scenarios.

### Header

#### Title

page-header

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/page-header?file=src/App.tsx)

```tsx lines theme={null}
import { Page } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "23rem" }}>
      <Page.Header title="Page Title" />
    </div>
  );
}

export default App;
```

#### With description

page-header-description

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/page-header-description?file=src/App.tsx)

```tsx lines theme={null}
import { Page, Text, Link } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "23rem" }}>
      <Page.Header
        title="Page Title"
        description={
          <Text>
            This page demonstrates all header elements with{" "}
            <Link href="#" appearance="primary">
              standard actions
            </Link>
          </Text>
        }
      />
    </div>
  );
}

export default App;
```

#### With breadcrumb

page-header-breadcrumbs

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/page-header-breadcrumbs?file=src/App.tsx)

```tsx lines theme={null}
import { Page } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "23rem" }}>
      <Page.Header
        title="Page Title"
        breadcrumbs={[
          { href: "/", children: "Home" },
          { children: "Current Page" },
        ]}
      />
    </div>
  );
}

export default App;
```

#### With chips

page-header-chips

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/page-header-chips?file=src/App.tsx)

```tsx lines theme={null}
import { Page } from "@servicetitan/anvil2";
import { core } from "@servicetitan/anvil2/token";

function App() {
  return (
    <div style={{ minWidth: "23rem" }}>
      <Page.Header
        title="Page Title"
        chips={[
          { label: "Status" },
          {
            label: "Draft",
            color: core?.primitive?.ColorPurple200.value,
          },
        ]}
      />
    </div>
  );
}

export default App;
```

#### With actions

page-header-actions

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/page-header-actions?file=src/App.tsx)

```tsx lines theme={null}
import { Page } from "@servicetitan/anvil2";

function App() {
  return (
    <div style={{ minWidth: "23rem" }}>
      <Page.Header
        title="Page Title"
        actions={{
          primary: {
            label: "Primary Action",
            onClick: () => console.log("Primary clicked"),
          },
          secondary: [
            {
              label: "Secondary Action",
              onClick: () => console.log("Secondary clicked"),
            },
          ],
        }}
      />
    </div>
  );
}

export default App;
```

### Main content

The main content area handles your primary content and, on its own, doesn't have any specific options. You'll always use it with a [Layout](https://anvil.servicetitan.com/docs/components/layout/web.md) component to get that consistent design you're looking for. Plus, its padding dynamically adjusts based on available space for the best experience.

A bare Flex or a lone table or Alert stretches past the header on wide screens. Wrap body content in a non-fluid Layout and Layout.Item, using `span={12}` for full width. Keep that Layout in sync with the header layout.

Place sections directly in the Layout Item. Introduce each section with a headline Text starting at `h2` and separate sections with spacing. Do not wrap the main content, or each section, in a Card; reserve the [Card](https://anvil.servicetitan.com/docs/components/card/web.md#when-to-use) for peer tiles, charts, and isolated groups without a section heading.

### Footer

The Footer stays at the bottom of the main content so actions such as Save and Submit remain in reach while the page scrolls. It accepts the same layout utilities as other layout surfaces, which is how a row of actions is aligned.

### Panel

The Panel supports a persistent layout and a toggleable layout. Choose between them using the [guidelines](https://anvil.servicetitan.com/docs/components/page/web.md#panel). Width is small, medium, or large, and medium is the default. Inner padding can be removed when the panel content supplies its own spacing. The panel also changes with [screen size](#panel-responsiveness).

The beta Page can divide a panel into a pinned header, one or more content regions, and a footer. A content region or the footer can stay pinned while the rest of the panel scrolls. Host a drill-down inside the panel when the supporting task has more than one step.

### Sidebar

The Sidebar houses [SideNav](https://anvil.servicetitan.com/docs/components/side-nav/web.md) and an optional heading. On wide screens it collapses, and the browser remembers that collapsed state between visits. On smaller screens the trigger shows the current page name, or Menu when no name is provided.

## Behavior

The Page responds to different screen sizes with responsive behaviors for header, panel, and sidebar while maintaining consistent layout structure.

### Header responsiveness

The Header is designed to conform to the width of your page content. By default, it uses the same fixed `max-width` of 1280px as the `Layout` component; set it to fluid only when the content `Layout` is fluid. It will always fit within the padding provided by the `Page` component.

On larger screens, if content overflows, each section of the header will wrap as needed, but this happens after reaching a maximum of 6 columns.

### Panel responsiveness

The Panel behaves differently in responsive mode depending on the option you choose. In persistent mode, the Panel moves below the main content, becoming a sequential part of the flow. However, in toggleable mode, its content shifts into a [Dialog](https://anvil.servicetitan.com/docs/components/dialog/web.md) that appears on top of the page.

### Sidebar responsiveness

On smaller screens, the Sidebar adapts by transforming into a dropdown that appears at the top of the page. The trigger label is the current page name, or Menu when the page does not provide one. Choosing a link closes the dropdown.

### Scrolling

The Page scrolls with the window. At wide sizes, a scrollbar on the content stays aligned with that window scroll.

The beta Page scrolls differently once the page is at least 1024px wide. Main content shares one scroll region inside the page, separate from the window. Below that width, the beta Page scrolls with the document.

## Usage Guidelines

| Standard | Requirement |
| - | - |
| DoUse Page as the foundation for every page | The Page component is the foundation for all ServiceTitan application pages, ensuring a unified look and feel across the platform. It provides a collapsible Sidebar for SideNavigation, and both toggleable and persistent panels, to build robust applications while maintaining that consistent design. |
| DoUse Page Header for any page header | When a page has a header, it must be the PageHeader component, which eliminates inconsistencies and ensures a uniform appearance. At its core it requires a title, and also supports a description for additional context, breadcrumbs for navigation, chips to display statuses, and action sets for primary and secondary page actions. |
| DoOpen a menu for a multi-path action | Where a single primary action leads to more than one path — creating a record manually or from a template, for instance — give the primary action `items` so it opens a menu instead of splitting the choice across two competing buttons. |
| DoUse the footer for persistent key actions | The Page footer is designed as a sticky element, positioned at the bottom of the screen to remain visible at all times. This makes it ideal for providing key page actions, such as "Save" or "Submit" buttons, that users need constant access to. |
| DoChoose toggleable or persistent panels | The Page panel gives you two options: a toggleable panel to provide details for a selected item in your main content, and a persistent panel to display additional content for the page itself. For instance, you would use a persistent panel to display a live activity feed or a tool palette that users need constant access to while they work on the main content. |
| Don'tDuplicate the CTA in main content | Don't duplicate or move the page CTA into main content. Keep primary and secondary page actions on the header. |

### How to Use

#### Sidebar

The Page sidebar is built to host navigation, specifically using a [SideNav](https://anvil.servicetitan.com/docs/components/side-nav/web.md) component, and can include a sidebar header for clear context. You aren't strictly restricted from adding other content, but it's highly recommended to keep it to navigation and titles only, to maintain a consistent experience and a recognizable purpose.

#### Panel

The persistent panel is designed to be self-contained and doesn't require any additional methods to be used. However, a toggleable panel must be opened through an action on the page's main content, such as clicking a button or a specific item. This ensures the panel only appears when it's directly relevant to the user's task.

## Content

Content within the Page should name the page once, then put supporting detail in the region that owns it.

Write the header title as a short noun phrase for the page. Keep the description to one or two lines of context, and keep primary actions in the header action area. Name those actions with verbs.

Write sidebar link labels as destinations. On smaller screens, the menu trigger should name the current page so people can tell which section is open.

Name a panel by the task it supports, such as Details or Activity. When the beta Page splits the panel into a header and footer, put the task name in the panel header and the commit actions in the panel footer.

## Keyboard Interaction

Users can navigate the Page using the keyboard.

| Key | Interaction |
| - | - |
| Tab | Moves through the sidebar toggle, header actions, main content, and an open panel |
| Enter or Space | Activates the focused button, including the sidebar toggle and the smaller-screen menu trigger |
| Escape | Cancels title editing and restores the previous title. Closes header menus |
| Page Up, Page Down | Scrolls an open controlled panel after that region is focused. On the beta Page at 1024px and wider, also scrolls the main content after that region is focused |

The desktop sidebar toggle is named Open Sidebar or Close Sidebar. Collapsed navigation is removed from the tab order. Below 1024px, the sidebar trigger opens a menu, and choosing a link closes it.

A closed toggleable panel is hidden from assistive technology. Below 1024px, an open toggleable panel is a dialog and keeps focus inside itself. A persistent panel stays on screen. It is still marked hidden from assistive technology, so do not rely on it as the only place for required information until that is resolved.

On the beta Page at 1024px and wider, the main content region is a tab stop. Focus it, then use Page Up and Page Down to scroll.

## Code

**Implementation**

page-playground-full-page

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/page-playground-full-page?file=src/App.tsx)

```tsx lines expandable theme={null}
import {
  Page,
  Text,
  SideNav,
  Link,
  Layout,
  Flex,
  Button,
} from "@servicetitan/anvil2";
import { useState } from "react";

function App() {
  const [panelOpen, setPanelOpen] = useState(false);

  const breadcrumbs = [
    { href: "/", children: "Home" },
    { href: "/section", children: "Section" },
    { children: "Current Page" },
  ];

  const chips = [{ label: "Status" }, { label: "Draft" }];

  const actions = {
    primary: {
      label: "Primary Action",
      onClick: () => console.log("Primary clicked"),
    },
    secondary: [
      {
        label: "Secondary Action",
        onClick: () => console.log("Secondary clicked"),
      },
    ],
  };

  return (
    <div style={{ width: "100vw" }}>
      <Page>
        <Page.Sidebar>
          <Page.SidebarHeader>
            <Text variant="headline" el="h2">
              Sidebar Title
            </Text>
          </Page.SidebarHeader>
          <SideNav>
            <SideNav.Link id="01" href="#" active>
              First Link
            </SideNav.Link>
            <SideNav.Link id="02" href="#">
              Second Link
            </SideNav.Link>
            <SideNav.Link id="03" href="#">
              Third Link
            </SideNav.Link>
          </SideNav>
        </Page.Sidebar>

        <Page.Header
          title="Page Header"
          breadcrumbs={breadcrumbs}
          chips={chips}
          actions={actions}
          preferenceAction={{
            "aria-label": "Open settings",
            onClick: () => console.log("Settings clicked"),
          }}
          description={
            <Text>
              This demonstrates all header elements with{" "}
              <Text inline>
                <Link href="#" appearance="primary">
                  standard actions
                </Link>
              </Text>
            </Text>
          }
        />

        <Page.Content style={{ height: "15rem" }}>
          <Layout>
            <Layout.Item span={12}>
              <Flex direction="column" gap="2">
                <Text variant="headline" el="h1" size="large">
                  Page Content
                </Text>
                <Text>
                  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
                  do eiusmod tempor incididunt ut labore et dolore magna aliqua.
                </Text>
                <Button onClick={() => setPanelOpen((p) => !p)}>
                  Toggle Panel Open
                </Button>
              </Flex>
            </Layout.Item>
          </Layout>
        </Page.Content>
        <Page.Panel open={panelOpen}>
          <Flex direction="column" gap="2">
            <Text variant="headline" el="h2">
              Page Panel
            </Text>
            <Text>Lorem ipsum dolor sit amet.</Text>
          </Flex>
        </Page.Panel>
        <Page.Footer>
          <Text>Page Footer</Text>
        </Page.Footer>
      </Page>
    </div>
  );
}

export default App;
```

## Common Examples

```tsx theme={null}
import { Page, SideNav, Text } from "@servicetitan/anvil2";

const ExampleComponent = () => (
  <Page>
    <Page.Sidebar>
      <Page.SidebarHeader>
        <Text variant="headline" el="h2">
          Sidebar Header
        </Text>
      </Page.SidebarHeader>
      <SideNav>
        <SideNav.Link id="01" href="#">
          Nav Link
        </SideNav.Link>
      </SideNav>
    </Page.Sidebar>

    <Page.Header title="Page Title" />

    <Page.Content>
      <Text>Page content.</Text>
    </Page.Content>
  </Page>
);
```

### Page components

In Anvil2, the `Page` component is used to create full-page layouts, along with a few sub-components listed below. When implementing Page, ensure you are using the [method](https://docs.st.dev/docs/frontend/monolith-layout/#anvil2-layout) defined by ST Platform.

* `Page`: the parent component to configure full-page layouts.
* `Page.Sidebar`: a collapsible section on the left side of the page. Below 1024px it becomes a popover opened from a trigger button.
* `Page.SidebarHeader`: used as the first child of `Page.Sidebar` to format a header using a [Text](https://anvil.servicetitan.com/docs/components/text/web.md) component. The heading is hidden from assistive technology while the sidebar is collapsed.
* `Page.Header`: top section of the page where page actions and title go. The title renders as an `h1`.
* `Page.Content`: wraps the primary content of the page, and renders as an HTML `section` element. Inside `Page` it portals into the shell's `main` landmark. Outside `Page` it renders in place. Use it with a [`Layout`](https://anvil.servicetitan.com/docs/components/layout/web) component so the content width matches `Page.Header`.
* `Page.Panel`: an optional section on the right side of the page. The root is a `div`. Omit `open` for a persistent panel. Pass `open` to show and hide it. Below 1024px, a controlled panel renders inside a [Dialog](https://anvil.servicetitan.com/docs/components/dialog/web.md).
* `Page.Panel.Header`, `Page.Panel.Content`, and `Page.Panel.Footer`: panel regions on the beta `Page` imported from `@servicetitan/anvil2/beta`. The header stays pinned. Content and footer can pin with `sticky`.
* `Page.Footer`: an optional footer inside the page. It renders an HTML `footer`, portals into the shell's footer slot, and supports [layout utility props](https://anvil.servicetitan.com/docs/utilities/web/layout-props.md). Outside `Page` it renders in place.

### Adding side bars to the page

The `Page.Sidebar` component is usually the first child of a `Page`, and adds a collapsible section to the left of the rest of the page content.

The `Page.Sidebar` should generally have a `Page.SidebarHeader` and `SideNav` as its `children` to create a consistent, responsive, and user-friendly in-page navigation experience.

The sidebar can be omitted if the current page does not require a secondary navigation.

```tsx theme={null}
<Page>
  <Page.Sidebar>
    <Page.SidebarHeader>
      <Text variant="headline" el="h2">
        Sidebar Header
      </Text>
    </Page.SidebarHeader>
    <SideNav>{/* ... side nav links */}</SideNav>
  </Page.Sidebar>
  <Page.Content>{/* ... page content */}</Page.Content>
</Page>
```

#### Sidebar collapsed state in local storage

In order to maintain the collapsed state of the page sidebar after refreshing or navigating to another page, a local storage value is saved to the `"sidebar-collapsed"` key. The key name can be overridden using the `localStorageKey` prop.

### Adding the header to the page

The `Page.Header` component should go right above the `Page.Content` section to ensure proper placement. It houses the page title, breadcrumb navigation, page classifications via chips, page actions, page description, and the page preference/setting action trigger.

The `Page.Header` is responsive in nature and has the `Layout` component built into it. It accepts the same properties you would set on your `Page.Content` layout via its own `layout` property, and it is recommended that you keep these values in sync to ensure constraints for the sections remain the same. Both default to a fixed layout with a `1280px` max width; omit `layout` unless the content `Layout` is `fluid` or uses a non-default `variant`.

The title in the `Page.Header` will always be an `h1` to adhere to SEO and accessibility best practices.

While the page description is a `ReactNode`, it is recommended to keep the descriptions simple by using `Text` and `Link` components. If a page action needs to be made for the page, use the actions property and not the description area.

```tsx theme={null}
<Page>
  <Page.Sidebar>{/* ... sidebar content */}</Page.Sidebar>

  <Page.Header
    title="Title"
    breadcrumbs={breadcrumbs}
    chips={chips}
    actions={actions}
    preferenceAction={{
      "aria-label": "Open settings",
      onClick: () => {},
    }}
    description={
      <Text>
        This page demonstrates all header elements with
        <Text inline>
          <Link href="#" appearance="primary">
            standard actions
          </Link>
        </Text>
      </Text>
    }
  />

  <Page.Content>{/* ... page content */}</Page.Content>
</Page>
```

#### User-editing for the header title text

To enable editing the page title, assign an object with an `onChange` handler to the `title` prop. This handler will be passed the updated title as a string, which can be used to manage the state for the title.

For accessibility, it is recommended to announce that the editing of the page title was successful, which can be accomplished by using a `toast`.

```tsx theme={null}
<Page>
  <Page.Header
    title={{
      text: title,
      onChange: (newTitle) => {
        setTitle(newTitle);
        toast.success({ title: "Page title edited" });
      }
    }}
  />
</Page>
```

### Adding panels to the page

Place `Page.Panel` as a child of `Page`, usually after the sidebar and content.

Omit `open` when the panel is always present. Pass `open` when the page shows and hides it. Below 1024px, a panel with `open` set renders inside a dialog instead of beside the content. A panel that omits `open` stays in the page flow and stacks under the main content at that width.

`size` sets the desktop width: `"small"` (16rem), `"medium"` (24rem, the default), or `"large"` (30rem). Panels pad their inner content. Set `noPadding` to remove that padding.

```tsx theme={null}
<Page>
  <Page.Sidebar>{/* ... sidebar content  */}</Page.Sidebar>
  <Page.Content>{/* ... page content */}</Page.Content>
  <Page.Panel open size="large">
    {/* ... panel content */}
  </Page.Panel>
</Page>
```

#### Drill-down inside a panel

`Page.Panel` hosts [DrillDown](https://anvil.servicetitan.com/docs/components/drilldown/web.md) the same way `Dialog` and `Drawer` do. The stack starts closed. `defaultDrillDownIndex` is the screen the first `next()` opens from that closed stack, and the index restored when the panel closes. It defaults to `0`. When a parent needs `setIndex`, `back`, or `next`, spread `drillDownProps` from `useDrillDown()` onto the panel.

```tsx theme={null}
import { Button, DrillDown, Page, useDrillDown } from "@servicetitan/anvil2";

function Example() {
  const { drillDownProps, next } = useDrillDown();

  return (
    <Page>
      <Page.Content>
        <Button onClick={() => next()}>Next</Button>
      </Page.Content>
      <Page.Panel open {...drillDownProps}>
        <DrillDown index={0}>
          <DrillDown.Content>First screen</DrillDown.Content>
        </DrillDown>
      </Page.Panel>
    </Page>
  );
}
```

#### Panel regions on the beta Page

The beta `Page` from `@servicetitan/anvil2/beta` adds `Page.Panel.Header`, `Page.Panel.Content`, and `Page.Panel.Footer`. The header is pinned above the scrolling regions. Set `sticky` on a content region to pin it under the header, and on the footer to pin it to the bottom of the panel. Footer actions wrap and align to the end.

```tsx theme={null}
import { Button, Flex, Text } from "@servicetitan/anvil2";
import { Page } from "@servicetitan/anvil2/beta";

function Example() {
  return (
    <Page>
      <Page.Header title="Records" />
      <Page.Content>{/* ... page content */}</Page.Content>
      <Page.Panel open>
        <Page.Panel.Header>
          <Text variant="headline" el="h2">
            Details
          </Text>
        </Page.Panel.Header>
        <Page.Panel.Content sticky>
          <Text>Filters stay visible while the list scrolls.</Text>
        </Page.Panel.Content>
        <Page.Panel.Content>{/* ... long content */}</Page.Panel.Content>
        <Page.Panel.Footer sticky>
          <Flex gap="3">
            <Button>Cancel</Button>
            <Button appearance="primary">Save</Button>
          </Flex>
        </Page.Panel.Footer>
      </Page.Panel>
    </Page>
  );
}
```

### Scrolling

`Page` from `@servicetitan/anvil2` scrolls with the window. At 1024px and wider, a virtual scrollbar on the content stays in sync with that window scroll.

Beta `Page` from `@servicetitan/anvil2/beta` uses one scroll region for every `Page.Content` at 1024px and wider. Those sections portal into a single `main` landmark, and that landmark scrolls on its own. It is a tab stop: focus it, then use Page Up and Page Down. Below 1024px, the beta page scrolls with the document and the landmark is not a tab stop.

### Adding a footer to the page

The `Page.Footer` component renders a semantic `footer` element within the page content area. It can be placed as a child of `Page` after the content or panel to add supplemental information or secondary actions.

```tsx theme={null}
<Page>
  <Page.Content>{/* ... page content */}</Page.Content>
  <Page.Footer>
    {/* ... footer content */}
  </Page.Footer>
</Page>
```

## Best Practices

### Use with Layout, Grid, and Flex components

The `Page` component is the first step in creating consistent and responsive page layouts using Anvil2. Three other components are available to configure dynamic layouts on a page:

* [Layout](https://anvil.servicetitan.com/docs/components/layout/web.md): define content max-width and column structure (usually the direct descendant of `Page.Content`) that matches `Page.Header`.
* [Grid](https://anvil.servicetitan.com/docs/components/grid/web.md): easily use [CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) to create dynamic content areas.
* [Flex](https://anvil.servicetitan.com/docs/components/flex/web.md): create [CSS Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) containers for simple to moderate content layout.

### Handling horizontal scrolling

The Page component does not allow horizontal scrolling at the page level by default. This prevents usability and accessibility issues, as users expect vertical scrolling and horizontal scrolling disrupts this mental model.

#### Migrating legacy pages

When migrating legacy pages that aren't responsive to the A2 Page layout, the recommended approach is to make the page responsive so it works well on all screen sizes. However, if making the page fully responsive is not possible, you can add horizontal scroll handling at the component level for wide content. Wrap specific components that need more horizontal space in a container with `overflow: auto`:

```tsx theme={null}
<div style={{ overflowX: "auto" }}>
  {/* Wide content that needs horizontal scrolling */}
</div>
```

This approach ensures that page headers, body text, and navigation remain accessible without horizontal scrolling, while allowing specific components to scroll independently when needed. Some components like [Data Table](https://anvil.servicetitan.com/docs/components/data-table/web) have horizontal scrolling built in.

## React Accessibility

`Page.Header` renders the title as an `h1`. Use one header per page.

The desktop sidebar toggle is a button named "Open Sidebar" or "Close Sidebar". Collapsed sidebar content uses `display: none` and `aria-hidden`, so keyboard focus stays on the toggle. Below 1024px the sidebar is a button named with `currentPageTitle`, or "Menu" when that prop is omitted. Activating it opens a popover, and choosing a link inside the popover closes it.

Title editing replaces the heading with a text field. Escape cancels and restores the previous title. Enter confirms the edit unless `onChange` returns `false`. The edit, confirm, and cancel controls have accessible names. Override them with `editButtonLabel`, `editFieldLabel`, `confirmButtonLabel`, and `cancelButtonLabel`.

Secondary actions that collapse into the overflow control use a button named "More actions". A primary action that passes `items` is a menu trigger. Both follow [Menu](https://anvil.servicetitan.com/docs/components/menu/web.md) keyboard behavior.

A panel with `open={false}` sets `aria-hidden` and `aria-disabled`. Below 1024px, a controlled panel renders in a dialog and uses that dialog's focus trap. On large screens, an open controlled panel sets `tabIndex={0}` on its scroll region so Page Up and Page Down can scroll it. Omitting `open` keeps the panel visible in the layout, and the panel still sets `aria-hidden` because that attribute is `!open`. Persistent panel content is visible and removed from the accessibility tree.

On the beta `Page`, the `main` landmark is a tab stop at 1024px and wider. Focus it, then use Page Up and Page Down to scroll the content.

**Page Props**

```tsx theme={null}
<Page>
  <Page.Sidebar>...</Page.Sidebar>
  <Page.Header title="Page Title" />
  <Page.Content>...</Page.Content>
</Page>
```

## `Page` Props

The `Page` component can accept any valid HTML `div` props.

**Page.Sidebar Props**

```tsx theme={null}
<Page.Sidebar localStorageKey="sidebar-collapse">
  <Page.SidebarHeader>Header</Page.SidebarHeader>
  <SideNav>...</SideNav>
</Page.Sidebar>
```

## `Page.Sidebar` Props

In addition to the props listed below, the `Page.Sidebar` component can accept any valid HTML `div` props.

**currentPageTitle** `string` (default: Menu)

Text on the trigger that opens the sidebar popover below 1024px. An empty
string also falls back to "Menu".

**data-tracking-id** `string`

On the desktop sidebar, applies this value to the sidebar root and skips
the generated tracking ID on the collapse button. The adaptive popover
does not receive it.

**localStorageKey** `string` (default: sidebar-collapsed)

`localStorage` key for the desktop collapsed state. See [Sidebar collapsed
state in local storage](#sidebar-collapsed-state-in-local-storage).

**Page.SidebarHeader Props**

```tsx theme={null}
<Page.SidebarHeader>
  <Text variant="headline" el="h2">
    Header
  </Text>
</Page.SidebarHeader>
```

## `Page.SidebarHeader` Props

The `Page.SidebarHeader` component can accept any valid HTML `div` props.

**Page.Header Props**

```tsx theme={null}
<Page.Header
  title="Page Title"
  breadcrumbs={breadcrumbs}
  chips={chips}
  description="Page description"
  actions={{
    primary: { label: "Primary Action", onClick: () => {} },
    secondary: [{ label: "Secondary Action", onClick: () => {} }],
  }}
  preferenceAction={{
    "aria-label": "Open settings",
    onClick: () => {},
  }}
/>
```

## `Page.Header` Props

The `Page.Header` component can accept any valid HTML `header` props, as well as the following:

**title** `string | \{ text: string; onChange?: (title: string) =\> void | boolean; editButtonLabel?: string; editFieldLabel?: string; confirmButtonLabel?: string; cancelButtonLabel?: string; \}` *required*

Page `h1`. A string is read-only. Pass an object with `onChange` to show an edit button. `onChange` receives the confirmed title and can keep the editor open by returning `false`.

Accessible names for the edit controls default to "Edit title", "Page title", "Update title", and "Cancel" for `editButtonLabel`, `editFieldLabel`, `confirmButtonLabel`, and `cancelButtonLabel`.

**actions** `\{ primary?: (Omit\<ButtonProps, "appearance"\> & \{ label: string \}) | (Omit\<Omit\<ButtonProps, "appearance"\> & \{ label: string \}, "onClick"\> & \{ items: (Omit\<ButtonProps, "appearance"\> & \{ label: string \})[] \}); secondary?: (Omit\<ButtonProps, "appearance"\> & \{ label: string \})[]; \}`

Primary and secondary controls. `label` is the visible text on every action. `appearance` is omitted because the header sets primary and secondary emphasis.

Three or more secondary actions collapse into a More actions menu. Two or more secondary actions also collapse when a primary action is present.

Pass `items` instead of `onClick` on `primary` to make that button open a menu. The trigger shows a trailing chevron, which replaces a consumer `icon`. Menu items keep a bare icon or `{ after }`. An `{ before }` icon is dropped, because `Menu.Item` only renders a leading icon.

**breadcrumbs** `BreadcrumbsLinkProps[]`

Links rendered above the title. Each item is spread onto `Breadcrumbs.Link`.

**chips** `ChipProps[]`

Status chips rendered under the title.

**description** `ReactNode`

Full-width content under the title and actions. Use text and links here. Put page actions in `actions`.

**layout** `LayoutProps`

Configures the header's built-in [Layout](https://anvil.servicetitan.com/docs/components/layout/web.md). When unset, the header uses the default fixed layout with a `1280px` max width. Match the `fluid` and `variant` values of the `Layout` inside `Page.Content`.

**preferenceAction** `(Omit\<ButtonProps, "aria-label" | "icon" | "appearance" | "size" | "className"\> & \{ "aria-label": string \}) | (Omit\<ButtonLinkProps, "aria-label" | "icon" | "appearance" | "size" | "className"\> & \{ "aria-label": string \})`

Settings control beside the title. `aria-label` is required and is also the tooltip text. The header supplies the settings icon, ghost appearance, small size, and class name. Pass `href` to render a link. Otherwise it renders a button.

**Page.Content Props**

```tsx theme={null}
<Page.Content>
  <Layout>
    <Layout.Item span={12}>Content</Layout.Item>
  </Layout>
</Page.Content>
```

## `Page.Content` Props

`Page.Content` renders a `section` and accepts HTML `section` attributes. Inside `Page`, it portals into the shell's `main` landmark and renders nothing until that slot mounts. Outside `Page`, it renders in place.

**Page.Panel Props**

```tsx theme={null}
<Page.Panel open={true} size="medium" noPadding={false}>
  Panel content
</Page.Panel>
```

## `Page.Panel` Props

`Page.Panel` renders a `div` and accepts HTML `div` attributes, as well as the following:

**defaultDrillDownIndex** `number` (default: 0)

Screen the first `next()` opens from a closed [DrillDown](https://anvil.servicetitan.com/docs/components/drilldown/web.md) stack. The panel starts with no active screen. Closing the panel restores this index.

**noPadding** `boolean` (default: false)

Removes the panel's inner padding.

**onDrillDownContextChange** `(context: DrillDownContextProps) =\> void`

Receives the panel's DrillDown context. Spread `drillDownProps` from `useDrillDown()` onto the panel instead of wiring this callback directly.

**open** `boolean`

Controlled visibility. Omit it for a persistent panel that stays in the layout. Below 1024px, a panel with `open` set renders inside a dialog.

**size** `"small" | "medium" | "large"` (default: medium)

Desktop width. `"small"` is 16rem, `"medium"` is 24rem, and `"large"` is 30rem.

**Page.Panel.Header Props**

```tsx theme={null}
import { Text } from "@servicetitan/anvil2";
import { Page } from "@servicetitan/anvil2/beta";

<Page.Panel.Header>
  <Text variant="headline" el="h2">
    Details
  </Text>
</Page.Panel.Header>
```

## `Page.Panel.Header` Props

Available on the beta `Page` from `@servicetitan/anvil2/beta`.

Renders a `header` pinned above the panel's scrolling regions. Its measured height offsets sticky `Page.Panel.Content`. Accepts HTML `header` attributes. It has no other props.

**Page.Panel.Content Props**

```tsx theme={null}
import { Page } from "@servicetitan/anvil2/beta";

<Page.Panel.Content sticky>
  Filters
</Page.Panel.Content>
```

## `Page.Panel.Content` Props

Available on the beta `Page` from `@servicetitan/anvil2/beta`.

Renders a `div` and accepts HTML `div` attributes, as well as the following:

**sticky** `boolean` (default: false)

Pins this region below `Page.Panel.Header` while the panel scrolls. The offset tracks the header's height.

**Page.Panel.Footer Props**

```tsx theme={null}
import { Button } from "@servicetitan/anvil2";
import { Page } from "@servicetitan/anvil2/beta";

<Page.Panel.Footer sticky>
  <Button appearance="primary">Save</Button>
</Page.Panel.Footer>
```

## `Page.Panel.Footer` Props

Available on the beta `Page` from `@servicetitan/anvil2/beta`.

Renders a `footer`. Actions wrap and align to the end. Accepts HTML `footer` attributes, as well as the following:

**sticky** `boolean` (default: false)

Pins the footer to the bottom of the panel.

**Page.Footer Props**

```tsx theme={null}
<Page.Footer>
  Footer content
</Page.Footer>
```

## `Page.Footer` Props

Renders an HTML `footer` and accepts HTML `div` attributes plus [layout utility props](https://anvil.servicetitan.com/docs/utilities/web/layout-props.md). Inside `Page`, it portals into the footer slot and renders nothing until that slot mounts. Outside `Page`, it renders in place. The footer sticks to the bottom of the content area.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.