Skip to content
IzzyBlazor 0.1.0-alpha.17

Enter all results ↑ ↓ move Esc close

Drawer

A dialog that slides in from an edge.

JavaScript off: what the HTML and CSS do on their own.

Usage

razor
<DialogTrigger For="filters">Filters</DialogTrigger>

<Drawer Id="filters" Title="Filters" Size="DialogSize.Sm" @bind-Open="_open">
    <Form Model="Filters" FormName="filters" OnValidSubmit="ApplyAsync">
        <DialogBody Gap="5">
            <RadioGroup @bind-Value="Filters!.Status" />
        </DialogBody>
        <FormActions>
            <Button Submit>Apply</Button>
        </FormActions>
    </Form>
</Drawer>

@* ApplyAsync ends with _open = false, which closes it on an interactive page. *@

Opening it

A drawer is a Dialog, so it opens the same three ways: a DialogTrigger with its Id, a link that adds ?dialog= and the id to the address, or @bind-Open from C#. The trigger and the link work on static pages, with JavaScript off too; Dialog's Overview shows each in every mode.

A form in a drawer

Put a Form in the drawer with its fields in a DialogBody and its FormActions last: the body fills the drawer's height, so the buttons sit at its bottom as the footer and still submit the form.

Preview

Showing all orders

Filters

Status

Code

Filters.razor
@using System.ComponentModel.DataAnnotations

<Row Gap="3" Wrap Align="Align.Center">
    <DialogTrigger For="order-filters" StartIcon="Lucide.SlidersHorizontal" Variant="ButtonVariant.Outline" Accent="Accent.Neutral">Filters</DialogTrigger>
    <TextBlock As="span" Size="TextSize.Sm" Color="Color.Muted">@_summary</TextBlock>
</Row>

<Drawer Id="order-filters" Title="Filters" Size="DialogSize.Sm" @bind-Open="_open">
    <Form Model="Model" FormName="order-filters" OnValidSubmit="Apply">
        @* DialogBody fills the drawer's height, so the footer after it sits at the bottom. *@
        <DialogBody Gap="5">
            <RadioGroup @bind-Value="Model!.Status" />
            <Checkbox @bind-Value="Model!.PaidOnly" />
            <Checkbox @bind-Value="Model!.Archived" />
        </DialogBody>
        @* Last in the form, the button is the drawer's footer and still submits it. Choices stay in
           the drawer until it's pressed; the close button leaves the shown orders as they are. *@
        <FormActions>
            <Button Submit>Show orders</Button>
        </FormActions>
    </Form>
</Drawer>

@code {
    [SupplyParameterFromForm(FormName = "order-filters")]
    private OrderFilters? Model { get; set; }

    private bool _open;
    private string _summary = "Showing all orders";

    protected override void OnInitialized() => Model ??= new();

    private void Apply()
    {
        var status = Model!.Status == OrderStatus.Any ? "all" : Model.Status.ToString().ToLowerInvariant();
        _summary = $"Showing {status} orders" + (Model.PaidOnly ? ", paid only" : "") + (Model.Archived ? ", archived too" : "");
        // On an interactive page C# closes the drawer; a post on a static page closes it anyway.
        _open = false;
    }

    public sealed class OrderFilters
    {
        public OrderStatus Status { get; set; }

        [Display(Name = "Paid orders only")]
        public bool PaidOnly { get; set; }

        [Display(Name = "Include archived orders")]
        public bool Archived { get; set; }
    }

    public enum OrderStatus { [Display(Name = "Any status")] Any, Pending, Shipped, Delivered }
}

Off: Show orders posts the form and loads the page again, with the drawer closed.

A post on a static page closes the drawer. On an interactive page the submit runs C# instead, so close it there with @bind-Open, as this example does. When a post can fail, reopen it with the errors as Dialog's form example does.

Edges and widths

Use Size for the width (Sm 20rem, Md 24rem by default, Lg 32rem, Xl 40rem, or Full) or Width for another, and Placement for another edge: Start, or Bottom and Top for a full-width sheet as tall as its content.

Preview

Order #1042

Two items, paid by card, shipping to London.

Order history

Oct 20
Placed, two items
Oct 21
Paid by card ending 4242
Oct 22
Shipped from Leeds

Share this order

Anyone with the link can see the order's items and status.

Code

Edges.razor
<Row Gap="3" Wrap Justify="Justify.Center">
    <DialogTrigger For="order-details" Variant="ButtonVariant.Outline" Accent="Accent.Neutral" StartIcon="Lucide.PanelRight">End</DialogTrigger>
    <DialogTrigger For="order-history" Variant="ButtonVariant.Outline" Accent="Accent.Neutral" StartIcon="Lucide.PanelRight">End, large</DialogTrigger>
    <DialogTrigger For="order-share" Variant="ButtonVariant.Outline" Accent="Accent.Neutral" StartIcon="Lucide.PanelBottom">Bottom</DialogTrigger>
</Row>

@* The default: the end edge, Size Md (24rem). *@
<Drawer Id="order-details" Title="Order #1042">
    <TextBlock>Two items, paid by card, shipping to London.</TextBlock>
</Drawer>

@* Lg, 32rem: room for a table or a longer form. On phones every size stops 3rem short of the
   screen's width. *@
<Drawer Id="order-history" Title="Order history" Size="DialogSize.Lg">
    <DescriptionList>
        <DescriptionItem Term="Oct 20">Placed, two items</DescriptionItem>
        <DescriptionItem Term="Oct 21">Paid by card ending 4242</DescriptionItem>
        <DescriptionItem Term="Oct 22">Shipped from Leeds</DescriptionItem>
    </DescriptionList>
</Drawer>

@* A sheet along the bottom, as tall as its content: suits phones. *@
<Drawer Id="order-share" Title="Share this order" Placement="DialogPlacement.Bottom">
    <TextBlock>Anyone with the link can see the order's items and status.</TextBlock>
</Drawer>

A menu from the start edge

Use Placement="DialogPlacement.Start" with Size="DialogSize.Sm" for navigation that slides in, such as a site's menu on phones.

Preview

Code

Nav.razor
<DialogTrigger For="site-menu" StartIcon="Lucide.PanelLeft" Variant="ButtonVariant.Outline" Accent="Accent.Neutral">Menu</DialogTrigger>

<Drawer Id="site-menu" Title="IzzyBlazor" Placement="DialogPlacement.Start" Size="DialogSize.Sm">
    <nav aria-label="Main">
        <NavMenu>
            <NavItem Href="docs" Icon="Lucide.BookOpen">Docs</NavItem>
            <NavItem Href="docs/components" Icon="Lucide.Blocks">Components</NavItem>
            <NavItem Href="demos" Icon="Lucide.LayoutDashboard">Demos</NavItem>
        </NavMenu>
    </nav>
</Drawer>

An app's navigation doesn't need one: in an AppShell, the AppSidebar becomes a drawer below its breakpoint by itself, opened by a SidebarToggle, with JavaScript off too.

Use a DialogBody for the content and a DialogFooter after it: the body fills the drawer's height, so the footer sits at its bottom. Without a body, the footer follows the content partway down.

Everything else is the dialog's: Title or a DialogHeader, DialogBody, DialogFooter and DialogClose, CloseOnBackdrop and ShowCloseButton, and InsideForm. Dialog's Overview covers each.

Mode support

What works in each mode, Drawer
FeatureOffStaticLive
EverythingPartlyYesYes

Off: JavaScript disabled. Static: a static SSR page with IzzyBlazor's script. Live: an interactive page (Server, WebAssembly or Auto). With JavaScript off, a drawer opened from the server isn't modal: focus can leave it, and Escape or a click outside closes it only in browsers with closedby (not Safari).

Accessibility

  • It's a native modal dialog: the page behind it is inert, Escape closes it, and focus returns to the DialogTrigger that opened it.
  • Title or a DialogHeader names it; without either, pass an aria-label.
  • The close button is named "Close" (CloseLabel).
  • With JavaScript off, a drawer opened from the server isn't modal: focus can leave it for the page behind. In browsers with the closedby attribute (Chrome and Edge 134, Firefox 141; not Safari), Escape closes it, and so does a click outside while CloseOnBackdrop is on.

Parameters

Drawer parameters
PropTypeDefault
TitleThe heading, which also names the dialog for screen readers.string?
CloseLabelThe close button's name.string?"Close"
ChildContentThe body, plus any DialogHeader and DialogFooter.RenderFragment?
ContentPaddingPadding inside the body, in spacing steps, in place of the dialog's.Responsive<int>
SizeThe width.DialogSizeDialogSize.Md
PlacementCenter (the default), a full-height drawer on the start or end side, or a full-width sheet along the top or bottom.DialogPlacementDialogPlacement.End
CloseOnBackdropCloses when the backdrop is clicked.booltrue
ShowCloseButtonShows the close button in the header.booltrue
InsideFormSet when the dialog is inside a <form>.boolfalse
QueryKeyThe query-string key that opens a dialog from a link: ?dialog=my-id renders the dialog with that id already open.string?"dialog"
@bind-OpenWhether it is open.boolfalse
IdThe element id.string?

See also

  • Dialog: a native modal dialog, opened by a trigger, a link or C#.
  • AppSidebar: a sidebar that becomes a drawer or panel on small screens.
  • Popover: non-modal content beside a trigger.