PageLayout
PageLayout replaces Page with a composition-first model where each panel owns its own state. Most migrations are mechanical: rename slots and props, replace removed APIs with the built-in alternatives, and update width value types.
Slots
The new slot names for PageLayout are mapped against their old counterparts in Page:
Page | PageLayout |
|---|---|
Page | PageLayout |
Page.Header | PageLayout.Header |
Page.Sidebar | PageLayout.Sidebar |
Page.Main | PageLayout.Content |
Page.DetailView | PageLayout.Details |
Page.PanelControlButton | removed - see below |
PageLayout.Content is required. All other slots are optional.
Prop changes
For both Sidebar and Details:
Page | PageLayout |
|---|---|
preferredWidth | defaultWidth |
dismissed | collapsed |
defaultDismissed | defaultCollapsed |
onDismissChange(state, reason) | onCollapsedChange(value) |
minWidth: number | minWidth: PanelWidth |
keepMounted | removed (state is alway preserved) |
integratedControls | removed (always enabled) |
maxWidthon bothSidebarandDetailslayout: 'split' | 'overlay'onDetailsdefaultLayout: 'split' | 'overlay'- on
DetailsonLayoutChange(value)onDetails
Default breakpoints
Use the PageLayout to structure app pages with persistent header, sidebar, content, and details panel slots, including responsive breakpoint handling and resizable panels.
The default breakpoints have changed:
| Panel | Page | PageLayout |
|---|---|---|
Sidebar | 640 px | 960 px |
Details | 1280 px | 600 px |
Review your existing breakpoint overrides after migrating. The new defaults are intentionally wider for the sidebar, and narrower for the details.
Reacting to breakpoint crossings
A breakpoint crossing won't overwrite a controlled collapsed or fire onCollapsedChange for a controlled panel. onBreakpointTransition(belowBreakpoint) fires on the first measurement and on every crossing; subscribe to it to keep collapsed in sync.
If you need a controlled panel to start collapsed on a narrow viewport, initialize your state from the callback rather than reading window.innerWidth yourself:
const [collapsed, setCollapsed] = useState(false);
<PageLayout.Sidebar
collapsed={collapsed}
onCollapsedChange={setCollapsed}
onBreakpointTransition={(belowBreakpoint) => {
if (belowBreakpoint) setCollapsed(true);
}}
>
PanelControlButton removed
Page.PanelControlButton has no direct replacement and expand controls on PageLayout.Sidebar are built-in and always enabled. For the PageLayout.Details use the PageLayout.Details.ControlBar or build your own button.
Custom control: Manage collapsed state with collapsed + onCollapsedChange, and wire your own button.
keepMounted behavior
PageLayout always keeps panel content mounted. Local state, form values, and scroll position persist across collapse and expand cycles automatically.
- If you used
keepMounted={true}, remove it. If you relied on unmount-on-close to reset state, reset explicitly inonCollapsedChange.
Width type
Page accepted broad PanelSize values. In contrast, PageLayout narrows panel width props to PanelWidth: number | "${n}%". Number values are treated as pixels. This applies to defaultWidth, minWidth, and maxWidth.
Before and after
// Before
import { Page } from '@dynatrace/strato-components/layouts';
<Page>
<Page.Header>Header</Page.Header>
<Page.Sidebar integratedControls>Sidebar</Page.Sidebar>
<Page.Main>Content</Page.Main>
<Page.DetailView>Details</Page.DetailView>
</Page>;
// After
import { PageLayout } from '@dynatrace/strato-components/layouts';
<PageLayout>
<PageLayout.Header>Header</PageLayout.Header>
<PageLayout.Sidebar>Sidebar</PageLayout.Sidebar>
<PageLayout.Content>Content</PageLayout.Content>
<PageLayout.Details>Details</PageLayout.Details>
</PageLayout>;
Controlled sidebar
const [collapsed, setCollapsed] = useState(false);
<PageLayout>
<PageLayout.Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
Sidebar
</PageLayout.Sidebar>
<PageLayout.Content>
<button onClick={() => setCollapsed((c) => !c)}>Toggle sidebar</button>
Content
</PageLayout.Content>
</PageLayout>;