The application shell around every page, with slots for banners, a header, a resizable side navigation, the scrolling content and trailing sidebars.
AppLayout is the shell an application page sits in: banners across the top, a header, a side
navigation on the leading edge, the scrolling content area with an optional footer under it, and
room for sidebars on the trailing edge. It fills its parent, so give the parent a height (usually the viewport).
Wrap the layout in a SideNavProvider and put a SideNav in the nav slot. Drag the edge just
outside the nav to resize it. On screens narrower than 1024px the nav opens from the toggle in the
header instead; see Small screens.
Every slot is optional and renders a div; pass as for a different element, such as
as="main" on the content.
AppLayoutBanners / PlumaAppLayoutBanners) stacks full-width banners above the
header.AppLayoutHeader / PlumaAppLayoutHeader) holds the top navigation bar. Its
height is the headerHeight variable below.AppLayoutNav / PlumaAppLayoutNav) hosts a SideNav.AppLayoutNavToggle / PlumaAppLayoutNavToggle) is the button that opens the
nav on small screens. Put it in the header, inside a SideNavProvider. It is hidden from 1024px
up.AppLayoutContent / PlumaAppLayoutContent) is the scrolling main area, with
rounded top corners. A Page inside it keeps its minimum width and scrolls horizontally when
the content area is narrower.AppLayoutFooter / PlumaAppLayoutFooter) sits under the content, in the content
column and outside the scroller, so whatever goes in it takes its own height instead of covering
the content.AppLayoutAside / PlumaAppLayoutAside) is a trailing region for sidebars. It comes
after the content, so a sidebar in it paints over the content at the same z-index. Below 1024px
it opens over the content when you pass isOpen; see Small screens.To hide the content while a route changes, put a style or className on the slot's child.
The nav slot reads and writes the collapsed state of the surrounding SideNavProvider, so the
provider's defaultIsCollapsed, isCollapsed and onCollapseChange work as they do for a
SideNav on its own. The edge to the right of the nav:
The edge only renders inside a SideNavProvider whose isCollapsible is true (the default).
The provider's onCollapseChange says which of these changed the state, and whether a drag's
change is a preview or final. See "What changed the state" in the SideNav docs.
Put an AppLayoutNavToggle in the AppLayoutHeader of a layout inside a SideNavProvider, and
below the lg breakpoint (1024px) the nav leaves the layout and the content takes the full width.
The nav opens over the content from the toggle and closes on Escape, on a click outside it, or from
the toggle again. Opening it moves focus to its first item; closing it returns focus to the toggle.
From 1024px up the layout is unchanged and the toggle is hidden.
A layout without the toggle keeps the nav in the layout at every width. The toggle needs the provider and renders nothing without one.
The open nav always shows expanded, without its collapse button or the resize edge. A collapsed state set on a wide screen comes back once the screen is wide again. The nav starts closed, and it closes whenever the viewport crosses 1024px.
The open state lives in the SideNavProvider, beside the collapsed state: isOpen,
defaultIsOpen and onOpenChange work like their collapse counterparts. Close the nav when the
route changes, so a link in it doesn't leave it covering the new page:
The aside can open over the content below 1024px too. Pass AppLayoutAside an isOpen prop and
an onDismiss callback, and open it from a button in the header. It opens from the trailing edge
and calls onDismiss on Escape, on a click outside it, and when the nav opens; set isOpen to
false there. Opening it moves focus to its first focusable item and closes the nav, and closing
it returns focus to the button that opened it. Without isOpen the aside keeps its column at every
width. From 1024px up the aside stays in view at the width of its content, and both props are
ignored.
Below lg the header has less room. Start it with AppLayoutNavToggle, then decide for each item
how it adapts:
display,
such as a workspace name that only shows from lg up.useMediaQuery in React or the MediaQuery
resource in Ember. A "Create" button, for example, becomes icon-only below lg. Keep its
aria-label and add a tooltip, so the action keeps its name.Below md, move the header's icon buttons into a "More" popover unchanged. Each one keeps its own
popover or menu, and a status indicator such as an unread dot travels with its button. Render the
same buttons in both places, so they stay in sync.
onNavWidthChange reports the nav's width in pixels, for something outside the layout that has to
line up with it, such as fixed-position chrome or an embedded app. It is called once on mount,
before the layout is first painted, whenever the nav collapses or expands, and for every width a
drag of the edge shows. The second argument's isDragging is true for a drag's live widths, and
false for the resting width, including the one a drag settles on. Turn off any easing of your own
while it is true, since the nav follows the pointer without easing.
The width is the one AppLayout drives: the drag width, or the collapsed or expanded width the
SideNavProvider's state picks (the expanded width when isCollapsible is false). Below 1024px,
where the nav opens over the content, it is 0. It does not reflect an override of the navWidth
variable.
The width comes from the SideNavProvider's state, not from the SideNav, so when mirroring it,
render the layout inside a provider and set isCollapsible on the provider rather than on the
SideNav.
An id on the content slot lands on the scrolling element, so another app can mount into it.
AppLayout sets these on its root element, so anything inside it can read them. Override one by
setting its public variable on the AppLayout or an ancestor.
| Key | Default | What it sizes |
|---|---|---|
navWidth | 220px expanded, 60px collapsed, and the live width while dragging; 0px below 1024px | The nav |
headerHeight | 44px | The header row |
contentRadius | radius['10'] | The content's top corners |
vars holds the public variables to override. resolvedVars holds the values in effect, which
is what to read when lining something else up with the layout, such as a fixed-position element
that has to clear the nav:
defaultVars holds the default values (navWidthExpanded, navWidthCollapsed, navWidthOverlay,
headerHeight, contentRadius).
PlumaAppLayout extends BoxDefault:'div'
What element or component should be rendered.
PlumaAppLayoutAside extends BoxWhether the aside is open over the content, below the lg breakpoint (1024px). Passing it,
true or false, makes the aside an overlay below lg; leave it out and the aside keeps its
column at every width. Opening it moves focus into it and closes the navigation if that is
open. It has no effect from lg up, where the aside is always in view at the width of its
content.
Called below the lg breakpoint when the open aside asks to close: on Escape, on a click
outside it, and when the navigation opens. Set isOpen to false in it.
PlumaAppLayoutBanners extends BoxPlumaAppLayoutContent extends BoxPlumaAppLayoutHeader extends Box