# Full page apps

Learn how to build a custom Stripe Dashboard page that provides a complete, multi-view interface for your users.

Full page apps give you an entire page in the Stripe Dashboard to work with, providing more room for complex layouts such as tabbed navigation, overview dashboards, list-to-detail flows, and data visualization. This guide describes key design patterns for building a full page app.

## Before you begin

- [Create an app](https://docs.stripe.com/stripe-apps/create-app.md) or use an existing one.
- Install SDK version `9.2.0-alpha.x` or later.
- Install the latest version of the [Stripe Apps CLI plugin](https://docs.stripe.com/stripe-apps/reference/cli.md#upgrade-the-cli).
- Add a `stripe.dashboard.fullpage` [viewport](https://docs.stripe.com/stripe-apps/how-ui-extensions-work.md#views-and-viewports) to your app.
- Review the [Stripe Apps UI components](https://docs.stripe.com/stripe-apps/components.md).
- Make sure your app has been added to the Private Preview allowlist for full page apps.

## Apply best practices

When designing a full page app, keep these principles in mind:

- **Organized**: Structure your app into logical tabs that represent distinct areas of your workflow. Make sure each serves a clear purpose so that users always know where to find what they need.
- **Progressive**: Lead with a high-level overview and let users discover details at their own pace. Avoid overwhelming users with all information at once.
- **Consistent**: Follow the layout and navigation patterns used by native Stripe Dashboard pages. Make users comfortable in your app without learning new interaction models.
- **Connected**: Connect your full page app to other parts of your Stripe Apps. If your app also uses a drawer view or settings page, provide clear pathways between them.

## Structure your page

Every full page app starts with a [FullPageView](https://docs.stripe.com/stripe-apps/components/fullpageview.md) as its root component. This component integrates your app with the Dashboard shell, providing the page header and optional page-level actions.

Organize your page content into tabs using [FullPageTabs](https://docs.stripe.com/stripe-apps/components/fullpagetabs.md) and `FullPageTab`. Each tab gets its own URL in the Dashboard (`/app/:appId/:tabId`), which means users can bookmark and deep-link to specific tabs. [Tabs](https://docs.stripe.com/stripe-apps/components/fullpageview.md#integration-with-dashboard-routing) also integrate with the Dashboard’s browser navigation automatically.

```jsx
import {
  FullPageView,
  FullPageTabs,
  FullPageTab,
} from '@stripe/ui-extension-sdk/ui';

import {Overview} from './views/Overview';
import {ItemsList} from './views/ItemsList';
import {ObjectsList} from './views/ObjectsList';

const App = () => {
  return (
    <FullPageView
      pageAction={{
        label: 'Create item',
        onPress: () => { /* handle creation */ },
      }}
    >
      <FullPageTabs>
        <FullPageTab id="overview" label="Overview" content={<Overview />} />
        <FullPageTab id="items" label="Items" content={<ItemsList />} />
        <FullPageTab id="objects" label="Objects" content={<ObjectsList />} />
      </FullPageTabs>
    </FullPageView>
  );
};
```

The `id` you assign to each `FullPageTab` becomes the `:tabId` segment of the URL. Choose IDs that are short, descriptive, and URL-friendly.

Include the optional `pageAction` prop on `FullPageView` to show a primary action (such as “Create item”) in the page header, accessible from any tab.

## Build an overview tab

An overview tab gives users a high-level summary of the most important activity across your app. It’s the landing page users see before navigating to specific tabs.

Use the [OverviewPage](https://docs.stripe.com/stripe-apps/components/overviewpage.md) component to structure this tab. `OverviewPage` supports [single-column](https://docs.stripe.com/stripe-apps/components/overviewpage.md#single-column) and [two-column](https://docs.stripe.com/stripe-apps/components/overviewpage.md#two-columnsfull-example) layouts, to arrange summary metrics, charts, and recent activity lists.

An effective overview tab typically includes:

- **Summary metrics**: Show a summary of key numbers (counts, rates, percentages). Use [Box](https://docs.stripe.com/stripe-apps/components/box.md) to lay these out in a horizontal row.
- **Trend charts**: Show how those metrics change over time. The [LineChart](https://docs.stripe.com/stripe-apps/components/linechart.md), [BarChart](https://docs.stripe.com/stripe-apps/components/barchart.md), and [Sparkline](https://docs.stripe.com/stripe-apps/components/sparkline.md) components are available from the `@stripe/ui-extension-sdk/ui/next` entry point. See [next-generation UI components](https://docs.stripe.com/stripe-apps/next-ui-components.md) for setup details.
- **Recent activity**: Highlight items requiring attention, linking directly into the relevant tab for action.

> Keep the overview tab focused on the information users check most frequently. If a metric doesn’t drive a decision or action, consider leaving it off the overview.

## Build a list view

List views are the primary way users browse and find items within a tab. Use the [DataTable](https://docs.stripe.com/stripe-apps/components/datatable.md) component to display structured data with sortable columns, status indicators, and clickable rows.

When building a list view:

- **Make rows interactive**: Wrap the primary identifier in each row (such as a name or ID) with a [Link](https://docs.stripe.com/stripe-apps/components/link.md) component that triggers navigation to the detail view.
- **Show status clearly**: Use [Badge](https://docs.stripe.com/stripe-apps/components/badge.md) components to indicate item states (active, pending, canceled) so users can scan the list and prioritize.
- **Handle empty states**: When the list has no data, display a message that explains both what appears here and, if appropriate, provides a call to action to create the first item.
- **Support pagination**: For large datasets, `DataTable` provides built-in pagination support.

## Navigate from list to detail

The key navigation pattern in a full page app is moving between a list of items and the detail view of a single item. When a user selects an item, the detail view replaces the entire tabbed layout — the tab bar is no longer visible. This gives the detail view the full page width and signals that the user has selected an item. A breadcrumb at the top of the detail view provides the path back to the list.

Manage this transition with React state at the top level of your app, conditionally rendering either the tabbed list or the detail view:

1. Track the selected item in component state at the root of your full page app.
1. When no item is selected, render `FullPageView` with `FullPageTabs` containing your list views.
1. When an item is selected, render `FullPageView` without `FullPageTabs`, showing the detail view directly.

```jsx
import {useState} from 'react';
import {
  FullPageView,
  FullPageTabs,
  FullPageTab,
} from '@stripe/ui-extension-sdk/ui';

import {Overview} from './views/Overview';
import {ItemsList} from './views/ItemsList';
import {ObjectsList} from './views/ObjectsList';
import {ItemDetail} from './views/ItemDetail';

type Item = {
  id: string;
  name: string;
  status: string;
};

const App = () => {
  const [selectedItem, setSelectedItem] = useState<Item | null>(null);

  if (selectedItem) {
    return (
      <FullPageView>
        <ItemDetail
          item={selectedItem}
          onBack={() => setSelectedItem(null)}
        />
      </FullPageView>
    );
  }

  return (
    <FullPageView
      pageAction={{
        label: 'Create item',
        onPress: () => { /* handle creation */ },
      }}
    >
      <FullPageTabs>
        <FullPageTab id="overview" label="Overview" content={<Overview />} />
        <FullPageTab
          id="items"
          label="Items"
          content={<ItemsList onSelect={(item: Item) => setSelectedItem(item)} />}
        />
        <FullPageTab id="objects" label="Objects" content={<ObjectsList />} />
      </FullPageTabs>
    </FullPageView>
  );
};
```

Inside the list, set the selected item when a user clicks a row:

```jsx
<Link onPress={() => onSelect(item)}>
  {item.name}
</Link>
```

Because the detail view replaces the tab bar, the `pageAction` on `FullPageView` can change to reflect the detail context (for example, switching from “Create item” to “Edit item”). The breadcrumb at the top of the detail view navigates back to the tabbed list.

> If your detail view supports further sub-navigation, such as viewing a related item’s details, you can extend this pattern by maintaining a navigation stack in state rather than a single selected item.

## Build a detail view

> The conditional rendering pattern described here is the recommended approach for the private preview.

The detail view is where users take action on a specific item. Because the detail view renders inside its own `FullPageView` without tabs, it has the full page width. A well-structured detail view provides all the context a user needs to make a decision and the controls to act on it.

### Add back navigation

At the top of the detail view, provide a breadcrumb so users can return to the list — the tab bar is no longer visible.

```jsx
<Box css={{stack: 'x', gap: 'xsmall', alignY: 'center'}}>
  <Link onPress={onBack}>Items</Link>
  <Inline css={{color: 'secondary'}}>›</Inline>
  <Inline>{item.name}</Inline>
</Box>
```

### Structure the content

For detail views with a mix of primary content and supplementary information, use a two-column layout. Place the main content (forms, activity history, line items) in the wider column, and supporting details (metadata, status summary, related links) in a narrower sidebar.

```jsx
<Box css={{stack: 'x', gap: 'xlarge'}}>
  <Box css={{width: '2/3', stack: 'y', gap: 'large'}}>
    {/* Primary content */}
  </Box>
  <Box css={{width: '1/3', stack: 'y', gap: 'large'}}>
    {/* Sidebar */}
  </Box>
</Box>
```

### Add actions

Position action buttons near the top of the detail view, next to the item title. Use `type="primary"` for the most common action and `type="destructive"` for irreversible actions. See the [Button](https://docs.stripe.com/stripe-apps/components/button.md) component for available button types.

```jsx
<Box css={{stack: 'x', distribute: 'space-between', alignY: 'center'}}>
  <Box css={{font: 'heading'}}>{item.name}</Box>
  <Box css={{stack: 'x', gap: 'small'}}>
    <Button type="primary" onPress={onApprove}>Approve</Button>
    <Button type="destructive" onPress={onReject}>Reject</Button>
  </Box>
</Box>
```

## Build create and edit flows

When users create a new item or edit an existing one, use a [FocusView](https://docs.stripe.com/stripe-apps/components/focusview.md) drawer. The drawer overlays the current view without interrupting the user’s place in the app.

For edit flows, pass the existing item’s data to the form component as initial values. Reuse the same form component for both creation and editing when the fields are the same.

Use [TextField](https://docs.stripe.com/stripe-apps/components/textfield.md), [Select](https://docs.stripe.com/stripe-apps/components/select.md), [DateField](https://docs.stripe.com/stripe-apps/components/datefield.md), and other [form components](https://docs.stripe.com/stripe-apps/components.md) to build your forms. Group related fields with [FormFieldGroup](https://docs.stripe.com/stripe-apps/components/formfieldgroup.md).

## Connect your full page app to other views

If your app includes a drawer view (`stripe.dashboard.drawer.default`) or a settings page, provide clear navigation paths between them.

### Navigate from a drawer to the full page

From a drawer or other Stripe Apps view, use the [Link](https://docs.stripe.com/stripe-apps/components/link.md) or [Button](https://docs.stripe.com/stripe-apps/components/button.md) component with a route descriptor object to navigate to your full page app. Both components accept the same `href` format:

```jsx
<Link
  href={{
    name: 'fullPage',
    params: {
      tabId: 'items',
    },
  }}
>
  View all items
</Link>

<Button
  href={{
    name: 'fullPage',
    params: {
      tabId: 'items',
    },
  }}
>
  Open items
</Button>
```

This deep-links to a specific tab in the full page view.

For imperative navigation (for example, redirecting after a form submission), use the `navigateToDashboardRoute` utility from `@stripe/ui-extension-sdk/utils`:

```jsx
import {navigateToDashboardRoute} from '@stripe/ui-extension-sdk/utils';

navigateToDashboardRoute({
  name: 'fullPage',
  params: {
    tabId: 'items',
  },
});
```

### Navigate from the overview to other tabs

Your overview tab can serve as a hub that links into specific items on other tabs. Use the same selection pattern to open an item, or use a route descriptor to navigate to a specific tab.

## Next steps

- See the [Stripe Apps UI component reference](https://docs.stripe.com/stripe-apps/components.md) for detailed API documentation on each component.
- Review the [next-generation UI components](https://docs.stripe.com/stripe-apps/next-ui-components.md) for data visualization and `DataTable`.
- See the [onboarding flow design pattern](https://docs.stripe.com/stripe-apps/patterns/onboarding-experience.md) for guidance on first-run flows.
