Skip to content
Shiny.Net.HttpServer v1 - A lightweight feature rich HTTP Server - Tunnels, Websockets, AOT, ASPNET Featureset, & Works EVERYWHERE!Let me see!

AppLayout

An application shell: a header, a footer, a left and a right panel, and the content between them. Panels collapse to nothing, to a narrow toolbar rail, or to their full width; they drag to resize between a minimum and maximum; and every region keeps its own scroll region.

Blazor only. For a Visual-Studio-style surface where the user rearranges tabbed, tear-off tool windows, see Docking instead — AppLayout is for a fixed shell whose panels you define.

  • NuGet downloads for Shiny.Blazor.Controls
Frameworks
Blazor
  • Three panel statesHidden, Toolbar (a narrow rail rendering whatever you put in ToolbarContent) and Shown, two-way bindable or driven from code.
  • Drag to resize – a handle on each panel’s inner edge, clamped to MinSize and MaxSize, with the final width reported back through @bind-Size.
  • Independent scroll regions – the panel body, the toolbar rail and the content each scroll on their own while the header, footer and a panel’s pinned header/footer stay put.
  • Configurable borders – a default width and colour on the shell that every region inherits and can override, or turn off per region.
  • Responsive – under a breakpoint measured on the shell (not the window), an expanded panel compacts to a rail and re-expands as a drawer floating over the content.
  • Persistence – opt in with PersistKey and a panel’s state and width survive a reload.
  • Order-independent markup – regions are placed by CSS grid areas, so you can write them in whatever order reads best.
claude plugin marketplace add shinyorg/skills
claude plugin install shiny-client@shiny
BLE, GPS, Jobs, Notifications, Push, HTTP Transfers, OBD, Music, Health, DataSync — iOS, Android, Windows, MacOS, Linux, Web
claude plugin install shiny-maui@shiny
Shell, Contact Store
claude plugin install controls@shiny
TableView, BottomSheet, PillView, ImageViewer, Scheduler, Markdown, Mermaid Diagrams — MAUI and Blazor
claude plugin install shiny-mediator@shiny
Mediator/CQRS with middleware and source generators
claude plugin install shiny-data@shiny
DocumentDB and Spatial data libraries
claude plugin install shiny-aspire@shiny
Orleans and Gluetun Aspire integrations
claude plugin install shiny-extensions@shiny
DI, Stores, Reflector, Localization, Hosting modules
copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install shiny-client@shiny
BLE, GPS, Jobs, Notifications, Push, HTTP Transfers, OBD, Music, Health, DataSync — iOS, Android, Windows, MacOS, Linux, Web
copilot plugin install shiny-maui@shiny
Shell, Contact Store
copilot plugin install controls@shiny
TableView, BottomSheet, PillView, ImageViewer, Scheduler, Markdown, Mermaid Diagrams — MAUI and Blazor
copilot plugin install shiny-mediator@shiny
Mediator/CQRS with middleware and source generators
copilot plugin install shiny-data@shiny
DocumentDB and Spatial data libraries
copilot plugin install shiny-aspire@shiny
Orleans and Gluetun Aspire integrations
copilot plugin install shiny-extensions@shiny
DI, Stores, Reflector, Localization, Hosting modules
View Skills Repository

Nothing to register — no service, no JS reference. The shell imports its own module.

@using Shiny.Blazor.Controls
<AppLayout Height="100dvh">
<AppLayoutHeader Height="56" Padding="0 16">
<button @onclick="() => nav.ToggleAsync()">☰</button>
<strong>My App</strong>
</AppLayoutHeader>
<AppLayoutPanel @ref="nav"
Side="PanelSide.Left"
@bind-State="navState"
@bind-Size="navWidth"
MinSize="180"
MaxSize="420"
CollapseBelow="900"
PersistKey="nav">
<HeaderContent><h4>Explorer</h4></HeaderContent>
<ToolbarContent>
<VStack Spacing="4" Align="StackAlign.Center" Padding="8 0">
<button title="Files">🗂</button>
<button title="Search">🔍</button>
</VStack>
</ToolbarContent>
<ChildContent>
@* this is the scrolling body *@
<NavMenu />
</ChildContent>
<FooterContent>…</FooterContent>
</AppLayoutPanel>
<AppLayoutContent Padding="20">
@Body
</AppLayoutContent>
<AppLayoutFooter Height="36" Padding="0 16">Ready</AppLayoutFooter>
</AppLayout>
@code {
AppLayoutPanel nav = default!;
PanelState navState = PanelState.Shown;
double navWidth = 260;
}
Parameter Type Default Description
Height string "100%" CSS height of the shell
HeaderSpan LayoutSpan Full Full runs the header edge to edge; Content insets it between the panels, which then run the full height
FooterSpan LayoutSpan Full The same for the footer
BorderWidth double 1 Default divider width for every region
BorderColor string? theme outline Default divider colour for every region
Background string? null CSS background shorthand
HostWidth double read-only Last measured width of the shell

Height (pixels; null sizes to the content), Border, BorderWidth, BorderColor, Background and Padding. The header draws its divider on the bottom, the footer on the top. Neither scrolls.

Scrollable (default true), Background and Padding. There are no border parameters — the header, footer and panels each draw the divider on their own edge.

Parameter Type Default Description
Side PanelSide Left Which edge the panel docks to
State PanelState Shown Hidden, Toolbar or Shown. Two-way via @bind-State
Size double 260 Expanded width in pixels. Two-way via @bind-Size, updated after a drag
MinSize / MaxSize double 140 / 640 Drag clamps
Resizable bool true Show the drag handle on the inner edge
ToolbarSize double 56 Rail width in Toolbar state
CollapseBelow double 0 Shell width under which the panel compacts. Zero disables it
CollapsedState PanelState Toolbar What an expanded panel collapses to
PersistKey string? null Saves state and width to localStorage
Scrollable bool true Give the body its own scroll region
Border, BorderWidth, BorderColor Divider on the edge facing the content
Background string? null CSS background shorthand
Padding string? null Applied to the body, so a pinned header or footer stays flush
Slot Renders
HeaderContent Pinned above the body
ChildContent The scrolling body
FooterContent Pinned below the body
ToolbarContent Replaces the body in Toolbar state — put anything you like in the rail
Member Description
SetStateAsync(PanelState) Move to a state, raising StateChanged
ToggleAsync() Toggle between Shown and CollapsedState
CurrentState The live state, which may differ from State after a collapse
CurrentSize The live width, which may differ from Size after a drag
IsOverlay True while the panel is floating as a compact drawer

CollapseBelow is measured against the shell, not the browser window — an AppLayout nested inside a card compacts when the card gets narrow.

Below the breakpoint an expanded panel drops to CollapsedState. Expanding it while compact floats it over the content as a drawer with a scrim; clicking the scrim dismisses it. Widening back past the breakpoint restores whatever state the panel had before it compacted.

Compacting never writes to PersistKey — it is a response to the viewport, not a preference the user expressed.

BorderWidth and BorderColor on the shell become the default for every region, resolved through CSS variables so theme tokens still apply. Each region overrides them individually, or removes its divider with Border="false".

<AppLayout BorderWidth="2" BorderColor="var(--shiny-color-primary)">
<AppLayoutHeader Border="false">…</AppLayoutHeader>
<AppLayoutPanel Side="PanelSide.Left" BorderColor="#e5e7eb">…</AppLayoutPanel>
</AppLayout>

A Hidden panel keeps its element so the width can animate, but is marked inert and aria-hidden — nothing inside it is focusable or announced. The drag handle carries role="separator" with aria-orientation="vertical". The width transition is dropped under prefers-reduced-motion.

  • Layout — the VStack, HStack and responsive Grid primitives.
  • Docking — user-rearrangeable, tear-off tool windows.