Width and scrolling
Static server-side rendering: no circuit, no WebAssembly.
A reading column
Use ContentWidth to center the content of the header, the main area and the footer on one column, such as Length.Rem(42) for articles, while the bars and their lines stay full width.
Preview
Code
System.ComponentModel.DataAnnotations
NavigationManager Navigation
@* A reading site with no sidebar. ContentWidth puts the header, the article and the footer on one
centered column, so their edges line up, while the bars and their lines stay full width. *@
<AppShell Variant="ShellVariant.Flat" ContentWidth="Length.Rem(42)">
<AppHeader>
<AppBrand Href="To(null)" Icon="Lucide.Zap" Label="Acme Journal" />
<Box Grow />
<SiteNav Label="Sections" Breakpoint="Breakpoint.Sm">
<NavItem Href='To("articles")'>Articles</NavItem>
<NavItem Href='To("guides")'>Guides</NavItem>
</SiteNav>
<IconButton Icon="Lucide.Search" Label="Search" Variant="ButtonVariant.Ghost" Accent="Accent.Neutral" />
</AppHeader>
<AppMain>
<Stack Gap="5" PaddingY="new() { Base = 8, Md = 12 }">
<TextBlock Size="TextSize.Xs" Weight="FontWeight.Semibold" Color="Color.Muted">ENGINEERING</TextBlock>
<Heading Level="1" Size="TextSize.Xl3">How we review a rule before it goes live</Heading>
<Row Gap="3" Align="Align.Center">
<Avatar Name="Grace Hopper" Size="AvatarSize.Sm" />
<TextBlock Size="TextSize.Sm" Color="Color.Muted">Grace Hopper · Oct 9, 2026 · 6 min read</TextBlock>
</Row>
<TextBlock>Every rule we ship decides real money. A wrong condition can hold a thousand honest refunds before anyone notices, so no rule goes live on one person's say-so.</TextBlock>
<Heading Level="2" Size="TextSize.Xl">Start with a dry run</Heading>
<TextBlock>A dry run replays last week's decisions through the new rule and lists what would have changed. Reviewers read that list before they read the rule: outcomes are what customers feel.</TextBlock>
<Alert Title="One reviewer is not enough">Rules that change more than 1% of last week's outcomes need two approvals.</Alert>
</Stack>
@* A full-bleed band: the color spans the main area, the content stays on the column. *@
<Section Bg="Color.FillSubtle">
<Stack Gap="3">
<Heading Level="2" Size="TextSize.Xl">Get the next article by email</Heading>
<Form Model="Model" FormName="reading-newsletter" OnValidSubmit="() => _subscribed = true">
<TextField @bind-Value="Model!.Email" Type="TextFieldType.Email" Placeholder="you@example.com" HideLabel>
<EndAddon><Button Submit>Subscribe</Button></EndAddon>
</TextField>
</Form>
if (_subscribed)
{
<TextBlock Size="TextSize.Sm" Color="Color.Muted">Thanks, Model!.Email is on the list.</TextBlock>
}
</Stack>
</Section>
</AppMain>
<AppFooter>
<Row Gap="4" Align="Align.Center" Wrap>
<TextBlock Size="TextSize.Sm" Color="Color.Muted" Grow>© 2026 Acme</TextBlock>
<TextBlock As="span" Size="TextSize.Sm"><Anchor Href='To("rss")'>RSS</Anchor></TextBlock>
<TextBlock As="span" Size="TextSize.Sm"><Anchor Href='To("privacy")'>Privacy</Anchor></TextBlock>
</Row>
</AppFooter>
</AppShell>
{
// Links within this example: ?view= in its own address, so they stay here.
private string To(string? page) => Navigation.GetUriWithQueryParameter("view", page);
[SupplyParameterFromForm(FormName = "reading-newsletter")]
private Newsletter? Model { get; set; }
private bool _subscribed;
protected override void OnInitialized() => Model ??= new();
public sealed class Newsletter
{
[Required(ErrorMessage = "Enter your email"), EmailAddress(ErrorMessage = "Enter an email like name@example.com"), Display(Name = "Email")]
public string? Email { get; set; }
}
}Static: Opening the menu moves focus to the first link. Subscribe posts without a reload.
A part's own ContentWidth wins over the shell's, for a wider header over a narrow article, and a Section in the main area still spans it, for a band of color from edge to edge.
Scrolling like a desktop app
Use ScrollMode="ShellScroll.Main" to fill the window and scroll only the main area, so the header, the sidebar and the footer never move.
Preview
Code
NavigationManager Navigation
@* A mail client that behaves like a desktop app: ScrollMode.Main fills the window and scrolls only
the message list, so the header, the folders and the footer never move. *@
<AppShell ScrollMode="ShellScroll.Main">
<AppHeader>
<SidebarToggle />
<AppBrand Href="To(null)" Icon="Lucide.Mail" Label="Acme Mail" />
<Box Grow />
<Button StartIcon="Lucide.Send" Size="ControlSize.Sm">Compose</Button>
<Avatar Name="Ada Lovelace" />
</AppHeader>
<AppSidebar Width="Length.Rem(14)">
<NavMenu>
<NavItem Href="To(null)" Icon="Lucide.Inbox" Badge="4" BadgeAccent="Accent.Primary">Inbox</NavItem>
<NavItem Href='To("starred")' Icon="Lucide.Star">Starred</NavItem>
<NavItem Href='To("sent")' Icon="Lucide.Send">Sent</NavItem>
<NavItem Href='To("archive")' Icon="Lucide.Archive">Archive</NavItem>
<NavItem Href='To("trash")' Icon="Lucide.Trash">Trash</NavItem>
</NavMenu>
</AppSidebar>
<AppMain>
<Heading Level="1" Class="ib-visually-hidden">Inbox</Heading>
<Stack Gap="0">
foreach (var (sender, subject, time, unread) in Messages)
{
<Row Gap="3" Align="Align.Center" PaddingX="5" PaddingY="3" BorderBottom="1">
<Avatar Name="sender" Size="AvatarSize.Sm" />
<Stack Gap="0" Grow MinWidth="Length.Px(0)">
<TextBlock Size="TextSize.Sm" Weight="(unread ? FontWeight.Semibold : FontWeight.Normal)">sender</TextBlock>
<TextBlock Size="TextSize.Sm" Color="Color.Muted" Truncate>subject</TextBlock>
</Stack>
<TextBlock Size="TextSize.Xs" Color="Color.Muted">time</TextBlock>
</Row>
}
</Stack>
</AppMain>
<AppFooter>
<TextBlock Size="TextSize.Sm" Color="Color.Muted">3.2 GB of 15 GB used · Synced 2 minutes ago</TextBlock>
</AppFooter>
</AppShell>
{
// Links within this example: ?view= in its own address, so they stay here.
private string To(string? page) => Navigation.GetUriWithQueryParameter("view", page);
}Static: At phone width the drawer opens without changing the address, and focus moves into it. Its close button, Escape or a tap beside it closes it and puts focus back on the menu button; following a link closes it too.
With the default, ShellScroll.Page, the window scrolls as on any web page: the header stays at the top and the sidebar's and aside's content stays in view beside the page.