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 for primary app destinations, s-page for the current page heading and actions, and page content for search that filters your app's resources.
Anchor to Migrate the app shellMigrate 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
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
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.
Anchor to Move each responsibilityMove 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. |
Anchor to Keep app search in contextKeep app search in context
First decide what the old search queried:
- If it searched resources owned by your app, place
s-search-fieldwith 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 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.
Classify every old TopBar.UserMenu and TopBar.Menu action by purpose:
- Put primary app destinations such as Settings in
s-app-navwhen they apply across the app. - Put resource or workflow actions in the relevant
s-pageaction 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.
Anchor to Remove TopBar and Frame stateRemove 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 for the complete shell teardown.
Anchor to Test the migrationTest the migration
- Open every
s-app-navdestination 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.
Anchor to Remove Polaris ReactRemove 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.
- App nav
- Page component
- Search field component
- Migrate Frame from Polaris React
- Migrate Navigation from Polaris React
- Migrate Page from Polaris React