# Page

Use Page to build the outermost "main content" container of a view. Page comes
complete with the ability to add the page title, along with actions, subtitle,
and a description as needed.

## Design & usage guidelines

Using Page is pretty straightforward - after any navigational elements, Page
should contain the content the user will be engaging with. If there is any sort
of footer, it should exist after Page.

### Title

The `title` prop supports both raw strings as well as any react node. This
allows complete flexibility, but with that comes responsibility around best
practices. A page should always start with an H1-level heading for
accessibility.

If you supply a string, the title will automatically be rendered as an H1
heading.

If you supply a custom element, you should include an H1-level heading within
it. Internally we use our [`<Heading level={1}>`](../Heading/Heading.md) component
when a string is supplied, so we recommend you use that if possible.

### Widths

#### Narrow

Use a `narrow` Page when the content is optimized for a single column, such as a
form design.

#### Standard

Use a `standard` Page for most "show" state layouts, as it allows for a mix of
contents in a multi-column layout while keeping things constrained so the user
does not have to track too far left-to-right as they interact.

#### Fill

A `fill` Page should be used when the content is optimized for wider views, such
as responsive dashboards and data visualizations, data tables, calendars, or
otherwise does not benefit from horizontal constraints.


## Composable Version (Web Only)

Page may be invoked with a subcomponent structure enabling a declarative style,
greater customization, and composability with other components. This is
particularly useful when you need real links in the "More Actions" menu (e.g.
for client-side router integration) or custom action elements.

```
<Page width="fill">
  <Page.Header>
    <Page.HeaderContent>
      <Page.TitleBar>
        <Page.Title>Clients</Page.Title>
        <StatusLabel label="Draft" status="warning" />
      </Page.TitleBar>
      <Page.Subtitle>Manage your client list</Page.Subtitle>
    </Page.HeaderContent>
    <Page.Actions>
      <Page.ActionPrimary>
        <Page.PrimaryButton label="New Client" onClick={handleCreate} />
      </Page.ActionPrimary>
      <Page.ActionSecondary>
        <Page.SecondaryButton label="Export" onClick={handleExport} />
      </Page.ActionSecondary>
      <Page.ActionMenu>
        <Page.Menu>
          <Menu.Item textValue="Import" onClick={handleImport}>
            <Menu.ItemIcon name="import" />
            <Menu.ItemLabel>Import</Menu.ItemLabel>
          </Menu.Item>
        </Page.Menu>
      </Page.ActionMenu>
    </Page.Actions>
  </Page.Header>
  <Page.Body>
    Page content here
  </Page.Body>
</Page>
```

#### Sub Components

***Page.Header (Required)***

Groups the title area and actions into the page header layout. Place non-action
content (title, subtitle) inside `Page.HeaderContent` and actions inside
`Page.Actions`.

***Page.HeaderContent (Optional)***

Wraps the title area (title, subtitle) inside `Page.Header`. Use whenever the
header contains more than one content element (e.g. title + subtitle, or
titlebar + subtitle) to keep them stacked vertically within the flex layout.

***Page.TitleBar (Optional)***

Flex container for the page title and optional sibling elements such as status
badges. Use when you need to display metadata alongside the heading. When no
metadata is needed, use `Page.Title` directly without wrapping in
`Page.TitleBar`.

```
<Page.TitleBar>
  <Page.Title>My Page Title</Page.Title>
  <StatusLabel label="Active" status="success" />
</Page.TitleBar>
```

***Page.Title (Required)***

Renders the page heading as an H1. When metadata is present alongside the title,
wrap both in `Page.TitleBar`.

***Page.Subtitle (Optional)***

Secondary text below the title. Always applies the default Text/Emphasis
styling. Works with any children type including translation components like
`<Trans>`.

For markdown formatting, provide `<Markdown>` explicitly:

```
<Page.Subtitle>
  <Markdown content="Everything but the **Kitchen Sink**" basicUsage />
</Page.Subtitle>
```

***Page.Intro (Optional)***

Introduction text between the header and body. Always applies the default Text
styling. Works with any children type including translation components.

For markdown formatting with links, provide `<Markdown>` explicitly:

```
<Page.Intro>
  <Markdown
    content="Read more at our [Help Center](https://help.getjobber.com)."
    basicUsage
    externalLink
  />
</Page.Intro>
```

***Page.Actions (Optional)***

Container for the action slots and menu. Applies the responsive action group
layout that stacks on small viewports and inlines on larger ones.

***Page.ActionPrimary / Page.ActionSecondary / Page.ActionMenu (Optional)***

Positional slots that control where action elements appear. Use these to wrap
either the default action buttons or fully custom elements.

```
{/* Default button via slot */}
<Page.ActionPrimary>
  <Page.PrimaryButton label="Create" onClick={handleCreate} />
</Page.ActionPrimary>

{/* Custom element via slot */}
<Page.ActionPrimary>
  <MyCustomButton />
</Page.ActionPrimary>
```

***Page.PrimaryButton / Page.SecondaryButton (Optional)***

Default action buttons with opinionated styling (`fullWidth`, primary or
secondary type). Use inside their respective slots.

***Page.Menu (Optional)***

The "More Actions" menu. Provides the default trigger button (kebab icon +
label) so consumers only need to supply `Menu.Item` children. Use inside
`Page.ActionMenu`.

This is the primary integration point for client-side routers. For example, if
you are using TanStack Router in the consumer app, you can wrap `Menu.Item` with
TanStack Router's `createLink()` to get right-click and ctrl-click support on
menu links:

```
// Create once in the consumer app
const TSRMenuItem = createLink(Menu.Item);

// Use inside Page.Menu
<Page.ActionMenu>
  <Page.Menu>
    <TSRMenuItem to="/clients/export" textValue="Export">
      <Menu.ItemIcon name="export" />
      <Menu.ItemLabel>Export</Menu.ItemLabel>
    </TSRMenuItem>
  </Page.Menu>
</Page.ActionMenu>
```

The trigger label defaults to "More Actions" and can be customized via the
`triggerLabel` prop.

***Page.Body (Required)***

Main content area of the page. Wraps children in a `Content` component.

## Migrating from Props-based to Composable

The props-based API remains fully supported. You only need to migrate if you
need composable actions (e.g. for client-side router links in the menu).

| Props-based                            | Composable                                                                                              |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `title="Clients"`                      | `<Page.Title>Clients</Page.Title>`                                                                      |
| `titleMetaData={<Badge />}`            | Place `<Badge />` as a sibling of `Page.Title` inside `Page.TitleBar`                                   |
| `subtitle="Text"`                      | `<Page.Subtitle>Text</Page.Subtitle>`                                                                   |
| `intro="Markdown **text**"`            | `<Page.Intro><Markdown content="Markdown **text**" basicUsage /></Page.Intro>`                          |
| `externalIntroLinks={true}`            | Pass `externalLink` to `<Markdown>` directly                                                            |
| `primaryAction={{ label, onClick }}`   | `<Page.ActionPrimary><Page.PrimaryButton label={label} onClick={onClick} /></Page.ActionPrimary>`       |
| `secondaryAction={{ label, onClick }}` | `<Page.ActionSecondary><Page.SecondaryButton label={label} onClick={onClick} /></Page.ActionSecondary>` |
| `moreActionsMenu={[...]}`              | `<Page.ActionMenu><Page.Menu>...</Page.Menu></Page.ActionMenu>`                                         |

### Example migration

Before:

```
<Page
  title="Clients"
  subtitle="Manage your client list"
  primaryAction={{ label: "New Client", onClick: handleCreate }}
  moreActionsMenu={[
    { actions: [{ label: "Import", icon: "import", onClick: handleImport }] },
  ]}
  width="fill"
>
  <Content>Page content here</Content>
</Page>
```

After:

```
<Page width="fill">
  <Page.Header>
    <Page.HeaderContent>
      <Page.Title>Clients</Page.Title>
      <Page.Subtitle>Manage your client list</Page.Subtitle>
    </Page.HeaderContent>
    <Page.Actions>
      <Page.ActionPrimary>
        <Page.PrimaryButton label="New Client" onClick={handleCreate} />
      </Page.ActionPrimary>
      <Page.ActionMenu>
        <Page.Menu>
          <Menu.Item textValue="Import" onClick={handleImport}>
            <Menu.ItemIcon name="import" />
            <Menu.ItemLabel>Import</Menu.ItemLabel>
          </Menu.Item>
        </Page.Menu>
      </Page.ActionMenu>
    </Page.Actions>
  </Page.Header>
  <Page.Body>
    Page content here
  </Page.Body>
</Page>
```

## Props-based Version

The original props-based API remains fully supported. If you don't need
composable actions, this is the simplest way to use Page:

```
<Page
  title="Clients"
  subtitle="Manage your client list"
  primaryAction={{ label: "New Client", onClick: handleCreate }}
  secondaryAction={{ label: "Export", onClick: handleExport }}
  moreActionsMenu={[
    {
      actions: [
        { label: "Import", icon: "import", onClick: handleImport },
        { label: "Archive", icon: "archive", onClick: handleArchive },
      ],
    },
  ]}
  width="fill"
>
  <Content>Page content here</Content>
</Page>
```


## Props

### Web

#### Page

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `title` | `ReactNode` | Yes | — | Title of the page.  Supports any React node. If a string is provided, it will be rendered as an H1 heading. Otherwise... |
| `externalIntroLinks` | `boolean` | No | — | Causes any markdown links in the `intro` prop to open in a new tab, i.e. with `target="_blank"`.  Can only be used if... |
| `intro` | `string` | No | — | Content of the page. This supports basic markdown node types such as `_italic_`, `**bold**`, and `[link name](url)`. |
| `moreActionsMenu` | `SectionProps[]` | No | — | Page title Action menu. |
| `primaryAction` | `ButtonActionProps` | No | — | Page title primary action button settings. |
| `secondaryAction` | `ButtonActionProps` | No | — | Page title secondary action button settings. |
| `subtitle` | `string` | No | — | Subtitle of the page. |
| `titleMetaData` | `ReactNode` | No | — | TitleMetaData component to be displayed next to the title. Only compatible with string titles. |
| `width` | `"fill" | "narrow" | "standard"` | No | `standard` | Determines the width of the page.  Fill makes the width grow to 100%.  Standard caps out at 1280px.  Narrow caps out ... |

#### Page.ActionMenu

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `ref` | `RefObject<HTMLDivElement>` | No | — |  |

#### Page.ActionPrimary

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `ref` | `RefObject<HTMLDivElement>` | No | — |  |

#### Page.ActionSecondary

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `ref` | `RefObject<HTMLDivElement>` | No | — |  |

#### Page.Menu

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `triggerLabel` | `string` | No | — |  |

#### Page.PrimaryButton

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `label` | `string` | Yes | — |  |
| `ariaLabel` | `string` | No | — |  |
| `disabled` | `boolean` | No | — |  |
| `icon` | `IconNames` | No | — |  |
| `loading` | `boolean` | No | — |  |
| `onClick` | `() => void` | No | — |  |
| `ref` | `RefObject<HTMLDivElement>` | No | — |  |

#### Page.SecondaryButton

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `label` | `string` | Yes | — |  |
| `ariaLabel` | `string` | No | — |  |
| `disabled` | `boolean` | No | — |  |
| `icon` | `IconNames` | No | — |  |
| `loading` | `boolean` | No | — |  |
| `onClick` | `() => void` | No | — |  |
| `ref` | `RefObject<HTMLDivElement>` | No | — |  |
