Migrate Tabs from Polaris React
App Home has no direct tab web component. Choose a replacement based on what the old tabs do instead of recreating the Tabs API.
- For a small in-page view switcher, use a segmented
s-button-groupwithgap="none". Drive the active button from app state, mark it visually, and exposearia-pressedon every option. - When each view has its own addressable page, use buttons with
hrefor real links and identify the current destination witharia-current="page". - When the interface requires tablist keyboard behavior, focus management, and associated tab panels, keep Polaris React during an incremental migration or implement and separately review the complete ARIA tabs pattern.
A visual marker alone isn't enough. The selected state, accessible state, content, and URL or app state must all agree.
Anchor to Map the tab behaviorMap the tab behavior
| Polaris React | Polaris web components | Migration notes |
|---|---|---|
tabs | Buttons in an s-button-group | Keep a stable ID for each view and use gap="none" for the documented segmented appearance. |
selected | App-owned view state | Use the same value to derive the active content, visible check marker, and aria-pressed. |
onSelect | Button onClick handlers | Update the stable view ID rather than an array index. |
| URL-backed tabs | Buttons with href or links | Preserve browser history and mark the current destination with aria-current="page". |
| ARIA tab panels | No direct equivalent | Preserve Polaris React until the complete tablist, tab, and tabpanel interaction can be implemented and reviewed. |
Anchor to Migration exampleMigration example
The example replaces a two-option in-page view switcher with the segmented button-group pattern documented by App Home. The active button has a check icon and exposes aria-pressed="true"; changing the view must also replace the associated product results.
Replacing tabs with a segmented view switcher
export function ProductViews({
view,
onViewChange,
}: {
view: 'all' | 'active';
onViewChange: (view: 'all' | 'active') => void;
}) {
return (
<s-button-group gap="none" accessibilityLabel="Product views">
<s-button
slot="secondary-actions"
variant="secondary"
icon={view === 'all' ? 'check' : undefined}
aria-pressed={view === 'all'}
onClick={() => onViewChange('all')}
>
All products
</s-button>
<s-button
slot="secondary-actions"
variant="secondary"
icon={view === 'active' ? 'check' : undefined}
aria-pressed={view === 'active'}
onClick={() => onViewChange('active')}
>
Active products
</s-button>
</s-button-group>
);
}
import {useState} from 'react';
import {Tabs} from '@shopify/polaris';
export function TabsMigrationExample() {
const [selectedTab, setSelectedTab] = useState(0);
return (
<Tabs
tabs={[{id: 'all', content: 'All'}, {id: 'active', content: 'Active'}]}
selected={selectedTab}
onSelect={setSelectedTab}
/>
);
}
Polaris web components
export function ProductViews({
view,
onViewChange,
}: {
view: 'all' | 'active';
onViewChange: (view: 'all' | 'active') => void;
}) {
return (
<s-button-group gap="none" accessibilityLabel="Product views">
<s-button
slot="secondary-actions"
variant="secondary"
icon={view === 'all' ? 'check' : undefined}
aria-pressed={view === 'all'}
onClick={() => onViewChange('all')}
>
All products
</s-button>
<s-button
slot="secondary-actions"
variant="secondary"
icon={view === 'active' ? 'check' : undefined}
aria-pressed={view === 'active'}
onClick={() => onViewChange('active')}
>
Active products
</s-button>
</s-button-group>
);
}Polaris React
import {useState} from 'react';
import {Tabs} from '@shopify/polaris';
export function TabsMigrationExample() {
const [selectedTab, setSelectedTab] = useState(0);
return (
<Tabs
tabs={[{id: 'all', content: 'All'}, {id: 'active', content: 'Active'}]}
selected={selectedTab}
onSelect={setSelectedTab}
/>
);
}Preview
Was this page helpful?