> ## 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.

# App Header - Web

> App headers provide top-level navigation and identity for Carto experiences.

> [!NOTE]
>
> For development, import `AppHeader` from
> `@servicetitan/anvil2-ai-kit`. For design, find it in the
> [AI Kit Web Component Library file](https://www.figma.com/design/QEIsArLktLeJAaFvcpw3PO/AI-Kit-Web-Component-Library).

ai-kit-app-header

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

```tsx lines theme={null}
import { AppHeader } from "@servicetitan/anvil2-ai-kit";
import {
  IconMinimize2,
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";

function App() {
  return (
    <div style={{ width: 900 }}>
      <AppHeader
        title="Title of conversation"
        leadingActions={[
          {
            id: "rail",
            icon: <IconPanelLeft />,
            label: "Toggle left panel",
            onPress: () => {},
          },
        ]}
        trailingActions={[
          {
            id: "artifact",
            icon: <IconPanelRight />,
            label: "Toggle right panel",
            onPress: () => {},
          },
          {
            id: "minimize",
            icon: <IconMinimize2 />,
            label: "Minimize",
            onPress: () => {},
          },
          {
            id: "close",
            icon: <IconX />,
            label: "Close",
            onPress: () => {},
          },
        ]}
      />
    </div>
  );
}

export default App;
```

## Options

AppHeader supports an optional centered title and two independent action clusters.

### Title

ai-kit-app-header-title

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

```tsx lines theme={null}
import { AppHeader, CartoTheme } from "@servicetitan/anvil2-ai-kit";
import { IconPanelLeft, IconX } from "@servicetitan/anvil2-icons";
import "@servicetitan/anvil2-ai-kit/styles.css";

function App() {
  return (
    <CartoTheme>
      <div
        style={{
          width: 900,
          display: "flex",
          flexDirection: "column",
          gap: 16,
        }}
      >
        {/* With title */}
        <AppHeader
          title="Title of conversation"
          leadingActions={[
            {
              id: "rail",
              icon: <IconPanelLeft />,
              label: "Toggle left panel",
              onPress: () => {},
            },
          ]}
          trailingActions={[
            { id: "close", icon: <IconX />, label: "Close", onPress: () => {} },
          ]}
        />

        {/* Without title — action-only header */}
        <AppHeader
          leadingActions={[
            {
              id: "rail",
              icon: <IconPanelLeft />,
              label: "Toggle left panel",
              onPress: () => {},
            },
          ]}
          trailingActions={[
            { id: "close", icon: <IconX />, label: "Close", onPress: () => {} },
          ]}
        />
      </div>
    </CartoTheme>
  );
}

export default App;
```

`title` renders centered between the two action clusters with `<h1>` semantics — it's the primary heading of the view the shell frames. Omit it for an action-only header; the flexible middle still reserves the centered space, whether or not a title fills it.

### Actions

ai-kit-app-header-actions

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

```tsx lines theme={null}
import { AppHeader, CartoTheme } from "@servicetitan/anvil2-ai-kit";
import {
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";
import "@servicetitan/anvil2-ai-kit/styles.css";

function App() {
  return (
    <CartoTheme>
      <div style={{ width: 900 }}>
        <AppHeader
          title="Title of conversation"
          leadingActions={[
            {
              id: "rail",
              icon: <IconPanelLeft />,
              label: "Toggle left panel",
              onPress: () => {},
            },
          ]}
          trailingActions={[
            {
              id: "artifact",
              icon: <IconPanelRight />,
              label: "Toggle right panel",
              onPress: () => {},
            },
            { id: "close", icon: <IconX />, label: "Close", onPress: () => {} },
          ]}
        />
      </div>
    </CartoTheme>
  );
}

export default App;
```

`leadingActions` render at the start (left in LTR) and `trailingActions` at the end. Reserve `leadingActions` for entry points into the surrounding layout — panel toggles, navigation — and `trailingActions` for surface-level controls, like minimize and close. Keep each action in the cluster it conventionally belongs to; don't relocate a panel toggle to the trailing side or a window control to the leading side. Consumers rely on this placement staying consistent across every Carto surface, so avoid rearranging it in your implementation.

## Behavior

The header adapts its title to available space and can present any action as disabled.

### Title Truncation

ai-kit-app-header-title-truncation

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

```tsx lines theme={null}
import { AppHeader, CartoTheme } from "@servicetitan/anvil2-ai-kit";
import {
  IconMinimize2,
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";
import "@servicetitan/anvil2-ai-kit/styles.css";

function App() {
  return (
    <CartoTheme>
      <div style={{ width: 420 }}>
        <AppHeader
          title="A very long conversation title that truncates once it collides with the action clusters"
          leadingActions={[
            {
              id: "rail",
              icon: <IconPanelLeft />,
              label: "Toggle left panel",
              onPress: () => {},
            },
          ]}
          trailingActions={[
            {
              id: "artifact",
              icon: <IconPanelRight />,
              label: "Toggle right panel",
              onPress: () => {},
            },
            {
              id: "minimize",
              icon: <IconMinimize2 />,
              label: "Minimize",
              onPress: () => {},
            },
            { id: "close", icon: <IconX />, label: "Close", onPress: () => {} },
          ]}
        />
      </div>
    </CartoTheme>
  );
}

export default App;
```

The title is single-line and truncates with an ellipsis rather than wrapping or pushing the action clusters. Its center shifts toward the side with fewer actions, since it fills the flexible space between both clusters.

### Disabled Actions

ai-kit-app-header-disabled-actions

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

```tsx lines theme={null}
import { AppHeader, CartoTheme } from "@servicetitan/anvil2-ai-kit";
import {
  IconMinimize2,
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";
import "@servicetitan/anvil2-ai-kit/styles.css";

function App() {
  return (
    <CartoTheme>
      <div style={{ width: 900 }}>
        <AppHeader
          title="Title of conversation"
          leadingActions={[
            {
              id: "rail",
              icon: <IconPanelLeft />,
              label: "Toggle left panel",
              onPress: () => {},
            },
          ]}
          trailingActions={[
            {
              id: "artifact",
              icon: <IconPanelRight />,
              label: "Toggle right panel",
              onPress: () => {},
              disabled: true,
            },
            {
              id: "minimize",
              icon: <IconMinimize2 />,
              label: "Minimize",
              onPress: () => {},
              disabled: true,
            },
            { id: "close", icon: <IconX />, label: "Close", onPress: () => {} },
          ]}
        />
      </div>
    </CartoTheme>
  );
}

export default App;
```

Set `disabled` on an individual action to dim it and block interaction while keeping it in place — useful when a panel toggle has nothing to reveal yet.

## Usage Guidelines

### When to Use

Use the App Header when:

* **View identity** – The experience needs a title bar that names the current view as its primary heading.
* **Shell-level actions** – The experience needs panel toggles, navigation, or surface controls such as minimize and close.
* **Shell headers** – An [App Shell](https://anvil.servicetitan.com/docs/components/app-shell/web.md) or [Window](https://anvil.servicetitan.com/docs/components/window/web.md) needs content for its header region. The App Header also works on its own.

### When not to use

Avoid using the App Header for:

* **Headings inside content** – The App Header title is the primary heading of the view. Use a headline [Text](https://anvil.servicetitan.com/docs/components/text/ai-kit.md) for headings within chat or panel content.
* **Actions with visible text labels** – Every App Header action is an icon-only button whose label appears as a tooltip.

## Keyboard Interaction

Users can navigate and activate header actions using standard keyboard controls.

| Key | Description |
| - | - |
| Tab | Moves focus to the next action |
| Enter / Space | Activates the focused action |

## Patterns that use this component

* [Agentic Experience Shell](https://anvil.servicetitan.com/docs/patterns/agentic-experience-shell/web.md) — Identifies the experience and provides shell-level actions

## Code

**Implementation**

ai-kit-app-header

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

```tsx lines expandable theme={null}
import { AppHeader } from "@servicetitan/anvil2-ai-kit";
import {
  IconMinimize2,
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";

function App() {
  return (
    <div style={{ width: 900 }}>
      <AppHeader
        title="Title of conversation"
        leadingActions={[
          {
            id: "rail",
            icon: <IconPanelLeft />,
            label: "Toggle left panel",
            onPress: () => {},
          },
        ]}
        trailingActions={[
          {
            id: "artifact",
            icon: <IconPanelRight />,
            label: "Toggle right panel",
            onPress: () => {},
          },
          {
            id: "minimize",
            icon: <IconMinimize2 />,
            label: "Minimize",
            onPress: () => {},
          },
          {
            id: "close",
            icon: <IconX />,
            label: "Close",
            onPress: () => {},
          },
        ]}
      />
    </div>
  );
}

export default App;
```

## Common Examples

To use the `AppHeader`, pass a `title` and configured icon-button actions. It works standalone or in the `header` slot of `AppShell`.

```tsx theme={null}
import { AppHeader } from "@servicetitan/anvil2-ai-kit";
import { IconMenu, IconX } from "@servicetitan/anvil2-icons";

function ExampleComponent() {
  return (
    <AppHeader
      title="Conversation"
      leadingActions={[
        { id: "menu", icon: <IconMenu />, label: "Open navigation" },
      ]}
      trailingActions={[
        { id: "close", icon: <IconX />, label: "Close" },
      ]}
    />
  );
}
```

### Actions

Each action renders as a ghost, icon-only `Button`. Its required `label` becomes the accessible name and tooltip.

ai-kit-app-header-actions

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

```tsx lines expandable theme={null}
import { AppHeader, CartoTheme } from "@servicetitan/anvil2-ai-kit";
import {
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";
import "@servicetitan/anvil2-ai-kit/styles.css";

function App() {
  return (
    <CartoTheme>
      <div style={{ width: 900 }}>
        <AppHeader
          title="Title of conversation"
          leadingActions={[
            {
              id: "rail",
              icon: <IconPanelLeft />,
              label: "Toggle left panel",
              onPress: () => {},
            },
          ]}
          trailingActions={[
            {
              id: "artifact",
              icon: <IconPanelRight />,
              label: "Toggle right panel",
              onPress: () => {},
            },
            { id: "close", icon: <IconX />, label: "Close", onPress: () => {} },
          ]}
        />
      </div>
    </CartoTheme>
  );
}

export default App;
```

Set `disabled` to prevent an action.

ai-kit-app-header-disabled-actions

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

```tsx lines expandable theme={null}
import { AppHeader, CartoTheme } from "@servicetitan/anvil2-ai-kit";
import {
  IconMinimize2,
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";
import "@servicetitan/anvil2-ai-kit/styles.css";

function App() {
  return (
    <CartoTheme>
      <div style={{ width: 900 }}>
        <AppHeader
          title="Title of conversation"
          leadingActions={[
            {
              id: "rail",
              icon: <IconPanelLeft />,
              label: "Toggle left panel",
              onPress: () => {},
            },
          ]}
          trailingActions={[
            {
              id: "artifact",
              icon: <IconPanelRight />,
              label: "Toggle right panel",
              onPress: () => {},
              disabled: true,
            },
            {
              id: "minimize",
              icon: <IconMinimize2 />,
              label: "Minimize",
              onPress: () => {},
              disabled: true,
            },
            { id: "close", icon: <IconX />, label: "Close", onPress: () => {} },
          ]}
        />
      </div>
    </CartoTheme>
  );
}

export default App;
```

### Title

The title renders as the view's `h1`. It fills the space between action clusters and truncates with an ellipsis before colliding with them.

ai-kit-app-header-title-truncation

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

```tsx lines expandable theme={null}
import { AppHeader, CartoTheme } from "@servicetitan/anvil2-ai-kit";
import {
  IconMinimize2,
  IconPanelLeft,
  IconPanelRight,
  IconX,
} from "@servicetitan/anvil2-icons";
import "@servicetitan/anvil2-ai-kit/styles.css";

function App() {
  return (
    <CartoTheme>
      <div style={{ width: 420 }}>
        <AppHeader
          title="A very long conversation title that truncates once it collides with the action clusters"
          leadingActions={[
            {
              id: "rail",
              icon: <IconPanelLeft />,
              label: "Toggle left panel",
              onPress: () => {},
            },
          ]}
          trailingActions={[
            {
              id: "artifact",
              icon: <IconPanelRight />,
              label: "Toggle right panel",
              onPress: () => {},
            },
            {
              id: "minimize",
              icon: <IconMinimize2 />,
              label: "Minimize",
              onPress: () => {},
            },
            { id: "close", icon: <IconX />, label: "Close", onPress: () => {} },
          ]}
        />
      </div>
    </CartoTheme>
  );
}

export default App;
```

On a standalone, full-shell, or fullscreen window surface, the title is centered between the clusters. A docked `Window` start-aligns it automatically through app-surface context.

### Window drag handle

Pass `dragHandleProps` from `useAppSurface()` to an action that moves a docked `Window`. The props are inert outside a docked window.

```tsx theme={null}
const { dragHandleProps } = useAppSurface();

<AppHeader
  leadingActions={[
    { icon: <IconGrip />, label: "Move window", dragHandleProps },
  ]}
/>;
```

## Internationalization

`AppHeader` has no component-owned strings. Localize the title and each action label before passing them to the component.

## React Accessibility

* The title is the primary `h1` for the framed view.
* Every action requires a label used for its `aria-label` and tooltip.
* Action buttons support pointer, touch, Enter, and Space through React Aria.

**AppHeader Props**

```tsx theme={null}
<AppHeader
  title="Conversation"
  leadingActions={leadingActions}
  trailingActions={trailingActions}
/>
```

## `AppHeader` Props

The `AppHeader` accepts the following props and forwards remaining `<header>` attributes except `children`, native `title`, and `style`.

**leadingActions** `AppHeaderAction[]`

Icon-button actions at the inline start.

**title** `string`

Primary view title. Omit for an action-only header.

**trailingActions** `AppHeaderAction[]`

Icon-button actions at the inline end.

**AppHeaderAction**

```tsx theme={null}
const action: AppHeaderAction = {
  id: "close",
  icon: <IconX />,
  label: "Close",
  onPress: close,
};
```

## `AppHeaderAction`

**icon** `IconElement` *required*

Icon shown in the action button.

**label** `string` *required*

Accessible name and tooltip content.

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

Disables the action.

**dragHandleProps** `WindowDragHandleProps`

Pointer and keyboard handlers that turn the action into a docked-window
drag handle.

**id** `string`

Stable React key. Defaults to `label`.

**onPress** `() =\> void`

Handles action activation.


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