Skip to content

App layout

import { AppLayout, NavItem, NavSection } from '@yarcl/react';
Skip to workspace content
Acme

Demo workspace

Projects

Workspace item 1
Workspace item 2
Workspace item 3
Workspace item 4
Workspace item 5
Workspace item 6
Workspace support

Resize the browser to try the mobile drawer. Type a draft, change pages, or toggle the compact desktop menu. The navigation keeps its state when the layout switches modes.

Pass navigation, navbar and footer content as React nodes. The main content goes in children. AppLayout provides the navigation landmark, so sections do not need another nav wrapper.

<AppLayout
desktopBreakpoint="lg"
sidebarWidth="sidebar"
navigationLabel="Workspace"
menuLabel="Open navigation"
skipLabel="Skip to main content"
navbar={<Text>Acme workspace</Text>}
footer={<Link href="/support">Help and support</Link>}
navigation={
<>
<NavSection title="Work">
<NavItem href="/projects" icon={<ProjectsIcon />} active>Projects</NavItem>
<NavItem href="/reports" icon={<ReportsIcon />}>Reports</NavItem>
</NavSection>
<NavSection title="Admin">
<NavItem href="/settings" icon={<SettingsIcon />}>Settings</NavItem>
</NavSection>
</>
}
>
<Heading level={1}>Projects</Heading>
<ProjectList />
</AppLayout>

The navbar stays above the content. The optional footer stays below it. Main content and long desktop navigation each scroll independently. The frame defaults to the dynamic viewport height, 100dvh.

desktopBreakpoint and sidebarWidth are required config keys with autocomplete. There is no assumed key name. The library defaults include lg and sidebar; choose keys from your own config when they differ.

export default defineConfig({
...defaults,
widths: { ...defaults.widths, workspace: '17rem' },
breakpoints: { ...defaults.breakpoints, desktop: '68rem' },
components: { ...defaults.components, AppLayout: { padding: 'md' } },
});
<AppLayout desktopBreakpoint="desktop" sidebarWidth="workspace"
navigationLabel="Workspace" navigation={<WorkspaceNavigation />}>
<ProjectList />
</AppLayout>

padding uses your spacing keys. An explicit prop wins over components.AppLayout.padding, then defaults.padding. Runtime themes update widths, padding and the breakpoint query. Themes must contain the keys the layout uses.

At the desktop breakpoint and above, navigation is a visible sidebar. Below it, the navbar shows a burger button that opens a left drawer. The breakpoint measures the browser viewport, including when the layout is embedded in a smaller container.

The drawer uses native modal behavior: opening moves focus inside, background controls become unavailable, and Escape, the close button or a backdrop click dismisses it. Closing returns focus to the menu button. Switching to desktop closes the mobile drawer and moves focus out of any hidden controls.

Ordinary primary link clicks close the drawer, including router handlers that prevent the browser navigation. Modified clicks, downloads and links targeting a new tab leave it open. Native anchor attributes and refs remain available on NavItem.

navigationLabel names the navigation and the drawer heading. menuLabel names the burger button and defaults to navigationLabel. closeLabel defaults to your configured close label. Provide localized strings for these props. skipLabel enables a keyboard-visible link that moves focus directly to the main area.

Set collapsed to display a desktop icon rail. It supplies the collapsed context to NavSection and NavItem. Navigation is expanded in the mobile drawer.

const [collapsed, setCollapsed] = useState(false);
<AppLayout desktopBreakpoint="lg" sidebarWidth="sidebar" collapsed={collapsed}
navigationLabel="Workspace" navigation={<WorkspaceNavigation />}
navbar={<Button aria-pressed={collapsed} onClick={() => setCollapsed(!collapsed)}>
Compact navigation
</Button>}>
<ProjectList />
</AppLayout>

Provide icons and text labels for rail items. The labels remain accessible and appear in tooltips on hover or keyboard focus. An explicit collapsed prop on a section or item overrides the layout context, including in mobile mode. Links without icons remain expanded; use the expanded sidebar when your navigation needs wide custom content.

Use defaultNavigationOpen for an initially open mobile drawer, or supply navigationOpen and respond to onNavigationOpenChange to control it. These props affect mobile navigation; desktop navigation stays visible. Entering desktop mode requests false through the change callback.

const [navigationOpen, setNavigationOpen] = useState(false);
<AppLayout desktopBreakpoint="lg" sidebarWidth="sidebar"
navigationLabel="Workspace" navigation={<WorkspaceNavigation />}
navigationOpen={navigationOpen} onNavigationOpenChange={setNavigationOpen}>
<ProjectList />
</AppLayout>

The navigation mounts once on the client and retains its React state and DOM when the drawer closes or the breakpoint changes. The frame and main content render on the server. Hydrate the component to enable navigation and drawer interaction.

Override the root height to fit a contained workspace. Use mainAs="section" when another main landmark surrounds it. mainProps supplies the content element’s native attributes, ref and scroll handlers. The root accepts native div attributes and its own ref.

const mainRef = useRef<HTMLElement>(null);
<AppLayout desktopBreakpoint="lg" sidebarWidth="sidebar"
navigationLabel="Preview workspace" navigation={<WorkspaceNavigation />}
mainAs="section" style={{ height: '30rem' }}
mainProps={{ ref: mainRef, 'aria-label': 'Projects', onScroll: handleScroll }}>
<ProjectList />
</AppLayout>

Scoped data-part selectors identify the frame’s navbar, sidebar, navigation, main and footer. The sidebar is the desktop container; navigation identifies the same content in both modes. Use your own classes to customize slot content and preserve config variables for colors and spacing.

Spacing props accept typed breakpoint maps. See responsive props for supported props, defaults and examples.

PropTypeDefaultDescription
desktopBreakpoint requiredBreakpointBreakpoint key at and above which the sidebar stays visible.
navigation requiredReactNodeNavigation links or sections, mounted once across desktop and mobile modes.
navigationLabel requiredstringAccessible name of the navigation and visible title of its mobile drawer.
sidebarWidth requiredWidthWidth key for the expanded desktop sidebar and mobile drawer.
childrenReactNodeMain content, which scrolls independently of the navbar and footer.
closeLabelstringconfig.labels.closeAccessible name of the mobile drawer's close button.
collapsedbooleanfalseCollapses desktop navigation to an icon rail. Mobile navigation stays expanded.
defaultNavigationOpenbooleanfalseInitial mobile drawer state.
footerReactNodeFooter content, kept outside the scrolling main area.
mainAs'main' | 'section''main'Main content element. Use section when embedding a layout within another main landmark.
mainPropsDetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>Native attributes, ref and scroll handlers for the main content element.
menuLabelstringnavigationLabelAccessible name of the mobile menu button.
navbarReactNodeTop navbar content, displayed beside the mobile menu button.
navigationOpenbooleanControlled mobile drawer state. Desktop navigation remains visible.
onNavigationOpenChange(open: boolean) => voidCalled when the mobile drawer opens or closes, including a desktop transition.
paddingResponsive<Spacing>configconfig.components.AppLayout.padding ?? config.defaults.paddingSpacing inside the navbar, navigation, main area and footer. Accepts a scalar or breakpoint map.
skipLabelstringText for an optional skip link, shown when keyboard focused.