---
title: Migrate TopBar from Polaris React
description: >-
  Remove the Polaris React TopBar and move navigation, page actions, search, and
  app-specific account destinations to their Polaris web components patterns.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/top-bar
  md: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/top-bar.md
api_name: app-home
---

# Migrate Top​Bar from Polaris React

Polaris React `TopBar` has no like-for-like replacement. Embedded apps already render inside the Shopify admin, which owns the global top bar, account menu, search, and responsive navigation controls.

Remove the in-iframe `TopBar`, then migrate only the app-specific responsibilities that it contained. Use [`s-app-nav`](https://shopify.dev/docs/api/app-home/app-bridge-web-components/app-nav) for primary app destinations, `s-page` for the current page heading and actions, and page content for search that filters your app's resources.

***

## Migrate the app shell

The following example moves product search into the Products page, promotes the create action into the page title area, and makes Settings and Support primary app destinations. It removes the duplicate user identity and mobile-navigation controls.

## Migrating an embedded app top bar

##### Polaris web components

```tsx
function ProductsApp({products, query, onQueryChange, onCreateProduct}) {
  const homeLink = {href: '/app', rel: 'home'} as const;

  return (
    <>
      <s-app-nav>
        <s-link {...homeLink}>Home</s-link>
        <s-link href="/app/products">Products</s-link>
        <s-link href="/app/settings">Settings</s-link>
        <s-link href="/app/support">Support</s-link>
      </s-app-nav>

      <s-page heading="Products">
        <s-button
          slot="primary-action"
          variant="primary"
          onClick={onCreateProduct}
        >
          Create product
        </s-button>

        <s-section heading="All products">
          <s-stack gap="base">
            <s-search-field
              label="Search products"
              value={query}
              onInput={(event) => onQueryChange(event.currentTarget.value)}
            />

            {products.length > 0 ? (
              <s-unordered-list>
                {products.map((product) => (
                  <s-list-item key={product.id}>
                    <s-link href={`/app/products/${product.id}`}>
                      {product.title}
                    </s-link>
                  </s-list-item>
                ))}
              </s-unordered-list>
            ) : (
              <s-paragraph>No products match this search.</s-paragraph>
            )}
          </s-stack>
        </s-section>
      </s-page>
    </>
  );
}
```

##### Polaris React

```tsx
import {Frame, Page, TopBar} from '@shopify/polaris';

export function ProductsApp({
  query,
  onQueryChange,
  onCreateProduct,
  onContactSupport,
  onNavigationToggle,
}) {
  const searchField = (
    <TopBar.SearchField
      value={query}
      onChange={onQueryChange}
      placeholder="Search products"
    />
  );

  const userMenu = (
    <TopBar.UserMenu
      name="Jaded Pixel"
      detail="store@example.com"
      initials="JP"
      actions={[
        {
          items: [
            {content: 'Account settings', url: '/app/settings'},
            {content: 'Contact support', onAction: onContactSupport},
          ],
        },
      ]}
    />
  );

  return (
    <Frame
      topBar={
        <TopBar
          searchField={searchField}
          userMenu={userMenu}
          showNavigationToggle
          onNavigationToggle={onNavigationToggle}
        />
      }
    >
      <Page
        title="Products"
        primaryAction={{content: 'Create product', onAction: onCreateProduct}}
      />
    </Frame>
  );
}
```

`s-app-nav` renders only in the Shopify admin shell, so this admin navigation example isn't available as an isolated live preview.

Keep `s-app-nav` mounted near the app root and let each route render its own `s-page`. Don't mount a separate app nav or page shell for each search result.

***

## Move each responsibility

| Polaris React TopBar responsibility | Polaris web components | Migration notes |
| - | - | - |
| App name and surrounding chrome | Remove | The Shopify admin identifies the embedded app and owns the global shell. |
| `showNavigationToggle` and `onNavigationToggle` | Remove | The Shopify admin owns responsive navigation controls. Remove state used only to open an in-iframe menu. |
| `searchField` | `s-search-field` in the relevant page | Keep app-resource search near the results it changes. Use route or URL state when the query should survive navigation. |
| `searchResults` and `searchResultsVisible` | A page table, list, or search-results section | Render normal page content from the same query state. Include loading, empty, and error states. |
| `onSearchResultsDismiss` | Clear the page search query | Reset associated filters and pagination only when that matches the existing behavior. |
| `secondaryMenu` | Slotted `s-page` actions or an `s-menu` | Keep frequent actions visible and put only related, secondary actions in a menu. |
| `userMenu` identity fields | Remove | Don't reproduce the merchant or shop identity that the Shopify admin already displays. |
| App-specific `userMenu.actions` | `s-app-nav`, page links, or page actions | Move destinations such as app settings and support to a discoverable location based on their scope. |

***

## Keep app search in context

First decide what the old search queried:

* If it searched resources owned by your app, place `s-search-field` with the table, list, or index page that displays those results.
* If it filtered only the current page, keep the query in that route and clear it when leaving the page.
* If it was intended to search the entire Shopify admin, remove it. Embedded apps can't replace or customize the admin's global search.

Connect the field to the backend query rather than filtering only the visible page when results are paginated. Reset pagination when the query changes, and use the debounce and cancellation pattern in the [Filters migration](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/filters#debounce-and-cancel-remote-requests) so an older response can't replace newer results. Render loading and retryable error states next to the results.

Don't move page-specific search into every route just to preserve the old top-bar position. Pages that don't search app resources shouldn't render an empty search control.

***

## Rehome account and menu actions

Classify every old `TopBar.UserMenu` and `TopBar.Menu` action by purpose:

* Put primary app destinations such as Settings in `s-app-nav` when they apply across the app.
* Put resource or workflow actions in the relevant `s-page` action slot.
* Put secondary, related page actions in `s-menu`, opened by a slotted page button.
* Put help links in the page or app navigation when merchants need persistent access to them.
* Remove sign-out, store-switching, merchant-profile, and other Shopify-account controls. The Shopify admin owns those actions.

Preserve authorization checks and pending, success, and error handling when moving an action. Changing its placement doesn't make its backend operation safe to run more than once.

***

## Remove Top​Bar and Frame state

Remove `TopBar`, `TopBar.SearchField`, `TopBar.UserMenu`, and `TopBar.Menu` after their app-specific responsibilities have moved. Also remove state and callbacks used only for these behaviors:

* Opening or closing the old user and secondary menus.
* Showing or dismissing overlaid search results.
* Opening in-iframe mobile navigation.
* Repeating the current shop's name, email, initials, or avatar.

If `Frame` still hosts Navigation, Toast, Loading, or ContextualSaveBar, migrate those responsibilities before removing it. Follow the [Frame migration](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/frame) for the complete shell teardown.

***

## Test the migration

* Open every `s-app-nav` destination directly and through browser navigation.
* Run page search through loading, results, empty, failed, cleared, and out-of-order response states.
* Verify search and pagination restore correctly after reload, back, and forward navigation.
* Run every moved page or menu action through success and failure, including repeated-submission protection.
* Test wide and narrow admin layouts and confirm that no app-owned top bar duplicates Shopify admin chrome.
* Verify page headings, action order, search labels, result announcements, and keyboard focus.

***

## Remove Polaris React

After every `TopBar` call site is migrated, remove its imports, shell-only state, menu descriptor builders, and CSS targeting TopBar internals. Remove `@shopify/polaris` only after no other route in scope imports it.

***

## Related guidance

* [App nav](https://shopify.dev/docs/api/app-home/app-bridge-web-components/app-nav)
* [Page component](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/page)
* [Search field component](https://shopify.dev/docs/api/app-home/web-components/forms/search-field)
* [Migrate Frame from Polaris React](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/frame)
* [Migrate Navigation from Polaris React](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/navigation)
* [Migrate Page from Polaris React](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/page)

***
