Skip to content
IzzyBlazor 0.1.0-alpha.20

Enter all results ↑ ↓ move Esc close

Asides

A column beside the main area: an outline, a page's own panel, and a drawer people open.

Static server-side rendering: no circuit, no WebAssembly.

An aside

Use AppAside for a column on the side opposite the sidebar, such as an outline with OnThisPage, which lists the main area's headings from the server. It shows from Breakpoint up, 1024px by default, and is Width wide, 15rem. Its content stays in view while the page scrolls, and scrolls on its own when it's taller than the window.

razor
<AppShell>
    ...
    <AppMain>@Body</AppMain>
    <AppAside>
        <OnThisPage />
    </AppAside>
</AppShell>

Give an aside that holds something other than an outline a Label, such as "Details", which names its landmark for screen readers. The docs demo has an outline beside an article.

A panel for the chosen row

Write an AppAside in a page, inside AppMain, for a panel that page alone has: it takes the shell's aside column, in place of the layout's aside if there is one, and pages without one leave no column. Use SmallScreen="AsideSmallScreen.Below" to put it under the main area below its breakpoint, rather than hide it.

Preview

Code

DetailPanel.razor
@inject NavigationManager Navigation

@* A list with the chosen row's details beside it. The page writes its own AppAside, which takes the
   shell's aside column; the rule and the panel's tab are in the address (?rule=, ?tab=), so every
   link works with JavaScript off. Below 768px the panel goes under the list (SmallScreen Below). *@
<AppShell>
    <AppHeader>
        <AppBrand Href="@To(null, null)" Icon="Lucide.ListChecks" Label="Acme Rules" />
        <Box Grow />
        <Avatar Name="Grace Hopper" />
    </AppHeader>

    <AppMain>
        <Stack Gap="4" Padding="new() { Base = 4, Md = 6 }">
            <Heading Level="1" Size="TextSize.Xl2">Rules</Heading>
            <Table Items="Rules" Caption="Rules" RowHref="r => To(r == Rules[0] ? null : r.Id, null)" RowLabel="r => r.Name" RowCurrent="r => r == Shown">
                <PropertyColumn Property="r => r.Name" />
                <TemplateColumn Title="Status">
                    <Badge Status="StatusOf(context.Status)" Dot>@context.Status</Badge>
                </TemplateColumn>
            </Table>
        </Stack>

        <AppAside Width="Length.Rem(20)" Breakpoint="Breakpoint.Md" SmallScreen="AsideSmallScreen.Below" Label="Rule" Padding="new() { Base = 4, Md = 5 }">
            <Stack Gap="4">
                <Stack Gap="1">
                    <Heading Level="2" Size="TextSize.Lg">@Shown.Name</Heading>
                    <Badge Status="StatusOf(Shown.Status)" Dot>@Shown.Status</Badge>
                </Stack>
                <Tabs Label="Rule" Size="ControlSize.Sm">
                    <TabLink Href="@To(ShownKey, null)" Label="Details" />
                    <TabLink Href='@To(ShownKey, "activity")' Label="Activity" />
                </Tabs>
                @if (Tab == "activity")
                {
                    <Stack Gap="3">
                        @foreach (var (when, what) in Activity)
                        {
                            <Stack Gap="0">
                                <TextBlock Size="TextSize.Sm">@what</TextBlock>
                                <TextBlock Size="TextSize.Xs" Color="Color.Muted">@when</TextBlock>
                            </Stack>
                        }
                    </Stack>
                }
                else
                {
                    <DescriptionList>
                        <DescriptionItem Term="Owner">@Shown.Owner</DescriptionItem>
                        <DescriptionItem Term="Hits (7 days)">@Shown.Hits.ToString("N0")</DescriptionItem>
                        <DescriptionItem Term="When">@Shown.When</DescriptionItem>
                    </DescriptionList>
                }
            </Stack>
        </AppAside>
    </AppMain>
</AppShell>

@code {
    [SupplyParameterFromQuery(Name = "rule")] private string? RuleKey { get; set; }
    [SupplyParameterFromQuery(Name = "tab")] private string? Tab { get; set; }

    private Rule Shown => Rules.FirstOrDefault(r => r.Id == RuleKey) ?? Rules[0];

    // The first rule is shown when none is chosen, and its links leave ?rule= out.
    private string? ShownKey => Shown == Rules[0] ? null : Shown.Id;

    // Links within this example: ?rule= and ?tab= in its own address, so they stay here.
    private string To(string? rule, string? tab) => Navigation.GetUriWithQueryParameters(new Dictionary<string, object?>
    {
        ["rule"] = rule,
        ["tab"] = tab,
    });
}

Keep the chosen row in the address (?rule= here) and link each row to it with the Table's RowHref, mark the chosen one with RowCurrent, and the panel's TabLinks work with JavaScript off. The page's aside moves into the shell only when the page and the layout render in the same mode; a page that is interactive on its own, in a static layout, keeps the aside where it's written, as a full-width block in the main area at every width.

A panel people open

Use SmallScreen="AsideSmallScreen.Drawer" for a panel that is a column on wide screens and a drawer from the end side below its breakpoint, opened by an AsideToggle. The toggle shows only while there is a drawer to open, so it can stay in the header on every page.

Preview

Code

AsideDrawer.razor
@inject NavigationManager Navigation

@* A panel people open when they want it. From 768px it's a column beside the page; below, it's a
   drawer from the end side, opened by the AsideToggle in the header (shown only then) and closed by
   its close button, a tap beside it or Escape. *@
<AppShell>
    <AppHeader>
        <AppBrand Href="@Navigation.GetUriWithQueryParameter("view", (string?)null)" Icon="Lucide.FileText" Label="Acme Docs" />
        <Box Grow />
        <AsideToggle Label="Show comments" />
    </AppHeader>

    <AppMain>
        <Stack Gap="4" Padding="new() { Base = 4, Md = 8 }">
            <Heading Level="1" Size="TextSize.Xl2">Refund policy</Heading>
            <TextBlock>Refunds over $500 are held for review by the payments team, who approve or decline them within one working day.</TextBlock>
            <TextBlock>A customer can ask for a refund up to 60 days after the order. After that, the order's owner decides.</TextBlock>
            <TextBlock>Gift cards aren't refunded; their balance stays on the card.</TextBlock>
        </Stack>
    </AppMain>

    <AppAside Width="Length.Rem(20)" Breakpoint="Breakpoint.Md" SmallScreen="AsideSmallScreen.Drawer" Label="Comments">
        <Stack Gap="4">
            <Heading Level="2" Size="TextSize.Md">Comments</Heading>
            @foreach (var (who, what) in Comments)
            {
                <Row Gap="3" Align="Align.Start">
                    <Avatar Name="@who" Size="AvatarSize.Sm" />
                    <Stack Gap="0">
                        <TextBlock Size="TextSize.Sm" Weight="FontWeight.Semibold">@who</TextBlock>
                        <TextBlock Size="TextSize.Sm" Color="Color.Muted">@what</TextBlock>
                    </Stack>
                </Row>
            }
        </Stack>
    </AppAside>
</AppShell>

Static: At phone width the drawer opens without changing the address, and focus moves to its close button. The close button, Escape or a tap beside it closes it and puts focus back on the panel button.

The drawer closes with its close button, a tap beside it or Escape, and focus goes back to the toggle. Any link to the aside's id (#ib-aside, its Id) opens it too, with JavaScript off.

Connection lost

Rejoining the server…