Skip to main content

PageLayout

    Use the PageLayout to structure app pages with persistent header, sidebar, content, and details panel slots, including responsive breakpoint handling and resizable panels.

    Import​

    import { PageLayout } from '@dynatrace/strato-components/layouts';

    Demo​

    Responsive behavior​

    Below its configured breakpoint, a panel renders as a Drawer instead of a split layout, to preserve screen space. The breakpoint only selects the render mode. It never overwrites a controlled collapsed.

    Out of the box​

    • PageLayout.Sidebar (uncontrolled): auto-collapses to a closed drawer below the breakpoint and reopens above it, so the navigation never covers the content. These auto-changes fire onCollapsedChange.
    • PageLayout.Details: never auto-collapses. defaultCollapsed (default false) is honored on every viewport, so an open, uncontrolled details panel shows as an open drawer below the breakpoint.
    • Any panel: subscribe to onBreakpointTransition(belowBreakpoint) to react to breakpoint changes. It fires on the first measurement and on every crossing, and never changes a controlled collapsed for you. Update collapsed yourself if you want to.

    Layout hierarchy​

    PageLayout.Sidebar and PageLayout.Details each have a breakpoint prop, but they measure different widths:

    • PageLayout.Sidebar - breakpoint is measured against the full PageLayout container width.
    • PageLayout.Details - breakpoint is measured against the combined width of the Content + Details area, to the right of the sidebar.

    This means the two breakpoints fire independently. When the sidebar is expanded, it consumes page width and shrinks the content + details area. A details panel with breakpoint={600} may collapse to a drawer even when the full page is wider than 600 px.

    The same rule applies to width props: minWidth, maxWidth, and defaultWidth on PageLayout.Details are percentages of the content + details area, not the full page.

    Preserve content state​

    Panel content is never unmounted. Component state, scroll positions, and form input values survive collapse and expand cycles without any configuration. To reset state on close, do so explicitly in onCollapsedChange, or conditionally render the slot's children.

    Built-in controls​

    Collapse and expand controls are always present in PageLayout.Sidebar and require no configuration. The collapse button appears on hover when the sidebar is expanded. When collapsed, an expand button takes its place.

    Overlay​

    Control the active mode with layout and onLayoutChange, or set an initial value with defaultLayout. In overlay mode, the details panel floats above the content area without displacing it. This maximizes the width of the content area but risks obstructing focused elements below the overlay. See Usage for further guidance.

    Details control bar​

    PageLayout.Details.ControlBar is optional. Place it at the top of the details panel to give users a close button and, above the breakpoint, a layout-mode toggle. See Control bar in Usage for placement and customization guidance.

    Compose it in a Flex to add a title, subtitle, and other elements for your details header.

    • Pass custom actions as children to extend the bar with panel-specific buttons. They appear before the default controls, separated by a divider.
    • The layout-toggle button is hidden in drawer mode.

    Resizing​

    Panels are resizable by default within their configured minWidth and maxWidth constraints. Set resizable to false to lock a panel to its configured width and hide the drag handle. Use this when the panel width is fixed by its content rather than by user preference.

    Content state​

    Panel content is never unmounted. Component state, scroll positions, and form input values persist across collapse and expand cycles automatically.

    To reset state when a panel closes, do so explicitly in onCollapsedChange. To re-initialize state on open, conditionally render the slot's children based on the collapsed state.

    Programmatic width control​

    Pass a width prop to PageLayout.Details to take ownership of the panel width. The panel follows the controlled value on every render; omitting width keeps the panel self-managing via defaultWidth.

    Use onResize to receive drag commits and keep the controlled value in sync.

    Mode-aware clamp​

    Controlled widths are clamped to the same bounds that apply to dragging:

    • overlay: the panel can grow to fill the container, so passing '100%' expands the panel to the full available width.
    • split: the panel is capped by the Content panel's minWidth (default '50%'), and by its own maxWidth when you set one, so the content area stays usable.

    This keeps a programmatic width consistent with what a user can reach by dragging.

    Maximize / restore button​

    Track a restoreWidth value to remember the pre-maximize state. A defined restoreWidth is what counts as "maximized":

    import { useState } from 'react';
    import { Button } from '@dynatrace/strato-components/buttons';
    import {
    PageLayout,
    type PanelWidth,
    } from '@dynatrace/strato-components/layouts';

    const DEFAULT_WIDTH: PanelWidth = '30%';

    const MaximizeExample = () => {
    const [width, setWidth] = useState<PanelWidth>(DEFAULT_WIDTH);
    const [restoreWidth, setRestoreWidth] = useState<PanelWidth>();

    const isMaximized = restoreWidth !== undefined;
    const maximize = () => {
    setRestoreWidth(width);
    setWidth('100%');
    };
    const restore = () => {
    setWidth(restoreWidth ?? DEFAULT_WIDTH);
    setRestoreWidth(undefined);
    };

    return (
    <PageLayout.Details
    defaultLayout="overlay"
    width={width}
    onResize={(px) => {
    setRestoreWidth(undefined);
    setWidth(px);
    }}
    >
    <PageLayout.Details.ControlBar>
    <Button onClick={() => (isMaximized ? restore() : maximize())}>
    {isMaximized ? 'Restore' : 'Maximize'}
    </Button>
    </PageLayout.Details.ControlBar>
    </PageLayout.Details>
    );
    };

    Collapse handling​

    The open/closed state of a details panel depends on what the user wants to see, not on the size of the window. The details panel stays where it's put across viewports and is never auto-collapsed by the breakpoint. Below the breakpoint, an open panel becomes an open drawer.

    • To open the panel programmatically, derive a controlled collapsed from your selection state: open on select, close on deselect.
    • To keep it closed on small screens, set defaultCollapsed or control collapsed and update it from onBreakpointTransition.

    Use with router​

    Slot components work correctly inside a router <Outlet />, a <Suspense> boundary, or any other wrapper, including components that conditionally render or lazy-load their children.

    A common pattern is a top-level PageLayout with the header and sidebar defined at the app layout level, and the Content and Details slots provided by individual route components:

    // App layout - defines the persistent layout frame
    const AppLayout = () => (
    <PageLayout>
    <PageLayout.Header>
    <AppHeader>{/* top-level navigation */}</AppHeader>
    </PageLayout.Header>
    <PageLayout.Sidebar>{/* sub-navigation */}</PageLayout.Sidebar>
    {/* Route components render their Content/Details slots here */}
    <Outlet />
    </PageLayout>
    );

    // Route component - only provides the slots it owns
    const DashboardRoute = () => (
    <>
    <PageLayout.Content>
    <DashboardTable />
    </PageLayout.Content>
    <PageLayout.Details>
    <PageLayout.Details.ControlBar />
    <DetailsPanel />
    </PageLayout.Details>
    </>
    );

    When a route lazy-loads its content, wrap it in <Suspense> and render a fallback inside PageLayout.Content to keep the layout stable while loading:

    const LazyDashboard = lazy(() => import('./DashboardRoute'));

    const AppLayout = () => (
    <PageLayout>
    <PageLayout.Header>...</PageLayout.Header>
    <PageLayout.Sidebar>...</PageLayout.Sidebar>
    <Suspense
    fallback={
    <PageLayout.Content>
    <ProgressCircle />
    </PageLayout.Content>
    }
    >
    <Outlet />
    </Suspense>
    </PageLayout>
    );
    Still have questions?
    Find answers in the Dynatrace Community