Skip to content
IzzyBlazor 0.1.0-alpha.18

Enter all results ↑ ↓ move Esc close

Dialog

A native modal dialog, opened by a trigger, a link or C#.

A static page with IzzyBlazor's script. No circuit.

Usage

razor
<DialogTrigger For="invite">Invite people</DialogTrigger>

<Dialog Id="invite" Title="Invite people">
    <Form Model="Invite" FormName="invite" OnValidSubmit="SendAsync">
        <TextField @bind-Value="Invite!.Email" autofocus />
        <FormActions>
            <DialogClose>Cancel</DialogClose>
            <Button Submit>Send invite</Button>
        </FormActions>
    </Form>
</Dialog>

Opening it

Use a DialogTrigger with the dialog's 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.

Preview

Open with a link

What's new in alpha.12

OnThisPage, PrevNext and CopyButton, for docs sites, and a quicker Table on large lists.

Code

Open.razor
@inject NavigationManager Navigation

<Row Gap="3" Wrap Align="Align.Center">
    <DialogTrigger For="whats-new" StartIcon="Lucide.Sparkles">What's new</DialogTrigger>
    <Button Href="@Navigation.GetUriWithQueryParameter("dialog", "whats-new")"
            Variant="ButtonVariant.Outline" Accent="Accent.Neutral">Open with a link</Button>
    @if (RendererInfo.IsInteractive)
    {
        <Button OnClick="() => _open = true" Variant="ButtonVariant.Ghost">Open from C#</Button>
    }
</Row>

<Dialog Id="whats-new" Title="What's new in alpha.12" @bind-Open="_open">
    <TextBlock>OnThisPage, PrevNext and CopyButton, for docs sites, and a quicker Table on large lists.</TextBlock>
    <DialogFooter>
        <DialogClose>Close</DialogClose>
    </DialogFooter>
</Dialog>

@code {
    private bool _open;
}

Static: The link opens it as a modal too, and closing it takes ?dialog= off the address.

QueryKey changes the key the link uses (closing takes it off the address), and null turns links off. Set Open from the server to show a dialog when the page loads, such as a notice after a post.

A form in a dialog

Put a Form in the dialog with its FormActions last: the buttons become the dialog's footer and still submit the form.

Preview

Project: Website

Rename project

3 to 40 characters, no slashes.

Code

Rename.razor
@using System.ComponentModel.DataAnnotations

<Row Gap="3" Wrap Align="Align.Center">
    <DialogTrigger For="rename" StartIcon="Lucide.Pencil" Variant="ButtonVariant.Outline" Accent="Accent.Neutral">Rename</DialogTrigger>
    <TextBlock As="span" Size="TextSize.Sm" Color="Color.Muted">Project: <strong>@_name</strong></TextBlock>
</Row>

<Dialog Id="rename" Title="Rename project" Size="DialogSize.Sm" @bind-Open="_open">
    <Form Model="Model" FormName="rename" OnValidSubmit="Save" OnInvalidSubmit="() => _open = true">
        <TextField @bind-Value="Model!.Name" Hint="3 to 40 characters, no slashes." autofocus />
        @* Last in the form, the buttons are the dialog's footer and submit the form. *@
        <FormActions>
            <DialogClose>Cancel</DialogClose>
            <Button Submit>Rename</Button>
        </FormActions>
    </Form>
</Dialog>

@code {
    [SupplyParameterFromForm(FormName = "rename")]
    private Project? Model { get; set; }

    private string _name = "Website";
    private bool _open;

    protected override void OnInitialized() => Model ??= new() { Name = _name };

    // Open again when the server finds an error, so it's seen; close when saved.
    private void Save()
    {
        _name = Model!.Name!;
        _open = false;
    }

    public sealed class Project
    {
        [Required(ErrorMessage = "Enter a name"), StringLength(40, MinimumLength = 3, ErrorMessage = "Use 3 to 40 characters")]
        [RegularExpression("[^/]*", ErrorMessage = "Leave out the slash")]
        public string? Name { get; set; }
    }
}

Static: Rename posts without a reload. When the server finds an error, the dialog opens again at the field.

A post on a static page closes the dialog: with JavaScript off the browser loads a new page, and with it the script closes the dialog as it posts. The script opens it again when the response has errors in it; set Open yourself when the post fails, as here with OnInvalidSubmit, so the errors show with JavaScript off too.

Use Title for a plain heading, or a DialogHeader for one with an icon or a badge. DialogBody lays the body out with Gap and the other layout props, and DialogFooter holds the buttons at the bottom.

Preview

Members 3
AL Ada Lovelace Owner
GH Grace Hopper Editor
AT Alan Turing Viewer

Code

Parts.razor
<DialogTrigger For="members" StartIcon="Lucide.Users">Members</DialogTrigger>

<Dialog Id="members">
    <DialogHeader>
        <Row Gap="2" Align="Align.Center">Members <Badge Pill>3</Badge></Row>
    </DialogHeader>
    <DialogBody Gap="3">
        @foreach (var (name, role) in Members)
        {
            <Row Gap="3" Align="Align.Center">
                <Avatar Name="@name" Size="AvatarSize.Sm" />
                <TextBlock As="span" Grow>@name</TextBlock>
                <Badge>@role</Badge>
            </Row>
        }
    </DialogBody>
    <DialogFooter>
        <DialogClose>Close</DialogClose>
    </DialogFooter>
</Dialog>

@code {
    private static readonly (string Name, string Role)[] Members =
    [
        ("Ada Lovelace", "Owner"),
        ("Grace Hopper", "Editor"),
        ("Alan Turing", "Viewer"),
    ];
}

The footer stays in view when the body scrolls. ContentPadding="0" on the dialog, or Flush on its DialogBody, runs a table or list to the edges.

Size and placement

Use Size for the width (Sm, Md, Lg, Xl or Full) or Width for another, and Placement for a panel at an edge. A Drawer is a dialog at the end edge.

Preview

Leave this page?

Your changes aren't saved.

Filters

A full-height panel at the end edge, like a Drawer.

Share

A sheet along the bottom, as tall as its content. Suits phones.

Code

Placement.razor
<Row Gap="3" Wrap Justify="Justify.Center">
    <DialogTrigger For="leave" Variant="ButtonVariant.Outline" Accent="Accent.Neutral">Small</DialogTrigger>
    <DialogTrigger For="filters" Variant="ButtonVariant.Outline" Accent="Accent.Neutral" StartIcon="Lucide.PanelRight">At the end</DialogTrigger>
    <DialogTrigger For="share" Variant="ButtonVariant.Outline" Accent="Accent.Neutral" StartIcon="Lucide.PanelBottom">Bottom sheet</DialogTrigger>
</Row>

<Dialog Id="leave" Title="Leave this page?" Size="DialogSize.Sm">
    Your changes aren't saved.
    <DialogFooter><DialogClose>Stay</DialogClose></DialogFooter>
</Dialog>

<Dialog Id="filters" Title="Filters" Size="DialogSize.Sm" Placement="DialogPlacement.End">
    A full-height panel at the end edge, like a Drawer.
</Dialog>

<Dialog Id="share" Title="Share" Placement="DialogPlacement.Bottom">
    A sheet along the bottom, as tall as its content. Suits phones.
</Dialog>

Closing

Use CloseOnBackdrop="false" to keep a dialog open when the backdrop is clicked, for a form someone could lose with a stray click, and ShowCloseButton="false" when the footer's buttons are enough. Escape, the close button and a DialogClose still close it.

Inside a form

Use InsideForm when the dialog has to sit in a form, such as a confirmation for that form's own submit button. Forms can't nest, so its close buttons then close it without submitting the form, and a submit button in it submits the form around it.

razor
<Form Model="Order" FormName="order" OnValidSubmit="PlaceAsync">
    ...
    <DialogTrigger For="confirm-order">Place order</DialogTrigger>
    <Dialog Id="confirm-order" Title="Place this order?" Size="DialogSize.Sm" InsideForm>
        Your card is charged $48.00.
        <DialogFooter>
            <DialogClose>Cancel</DialogClose>
            <Button Submit>Place order</Button>
        </DialogFooter>
    </Dialog>
</Form>

ConfirmButton does this in one component.

Dialogs from code

Use DialogService on an interactive page to open a dialog from C# and wait for how it ends: ConfirmAsync asks a question, and OpenAsync shows a component of yours. A DialogHost on the page or its layout shows them.

Always Live

Preview

Ada Lovelace is a member.

Code

Service.razor
@inject DialogService Dialogs

@* One DialogHost per interactive page or layout shows the service's dialogs. *@
<DialogHost />

<Row Gap="3" Wrap Align="Align.Center">
    <Button OnClick="RemoveAsync" Accent="Accent.Danger" Variant="ButtonVariant.Outline" StartIcon="Lucide.UserMinus">Remove member</Button>
    <TextBlock As="span" Size="TextSize.Sm" Color="Color.Muted">@_result</TextBlock>
</Row>

@code {
    private string _result = "Ada Lovelace is a member.";

    private async Task RemoveAsync()
    {
        var yes = await Dialogs.ConfirmAsync("Ada Lovelace loses access to this project.", "Remove member?",
            new() { ConfirmText = "Remove", Accent = Accent.Danger });
        _result = yes ? "Removed Ada Lovelace." : "Kept Ada Lovelace.";
    }
}

The page awaits the result:

EditPage.razor
@inject DialogService Dialogs

@code {
    private async Task EditAsync()
    {
        var result = await Dialogs.OpenAsync<EditName>("Edit name", new() { ["Name"] = _name });
        if (!result.Canceled) _name = result.DataAs<string>();
    }
}

The component closes the dialog with it, through the DialogInstance it's given:

EditName.razor
<form @onsubmit="() => Dialog.Close(_name)">
    <input @bind="_name" aria-label="Name" autofocus />
    <DialogFooter>
        <Button OnClick="Dialog.Cancel" Variant="ButtonVariant.Outline" Accent="Accent.Neutral">Cancel</Button>
        <Button Submit>Save</Button>
    </DialogFooter>
</form>

@code {
    [CascadingParameter] public DialogInstance Dialog { get; set; } = null!;

    [Parameter] public string? Name { get; set; }

    private string? _name;

    protected override void OnInitialized() => _name = Name;
}

Static pages can't open anything from code: use a trigger, a link or Open there.

Parts

Written inside Dialog. The API page lists their parameters.

  • DialogTrigger: a Button that opens a Dialog by id.
  • DialogHeader: Heading content for a Dialog in place of its Title, such as a title with an icon or a badge.
  • DialogBody: a Dialog's body with layout props: a column with Gap, Align, Justify and Direction, like Stack.
  • DialogFooter: the row of buttons at the bottom of a Dialog or Drawer, usually a DialogClose and the main action.
  • DialogClose: a Button that closes the Dialog it is in.
  • DialogHost: where DialogService shows its dialogs.

Mode support

What works in each mode, Dialog
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 dialog 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). Written inside a Toolbar, it centers on the toolbar rather than the window.

Accessibility

  • Title (an h2) or a DialogHeader (a level-2 heading) names the dialog with aria-labelledby. Without either it has no name: pass an aria-label.
  • The close button is named "Close" (CloseLabel). Escape closes the dialog even when ShowCloseButton and CloseOnBackdrop are off.
  • DialogTrigger is a native button with aria-haspopup="dialog", and focus comes back to it when the dialog closes.
  • Put autofocus on the field to start in. After an enhanced post that comes back with errors, the dialog reopens with focus on the first invalid field.
  • With JavaScript off, a dialog opened from the server (?dialog=id, Open) 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. DialogTrigger needs no script in current browsers.
  • DialogService's dialogs (shown by DialogHost) are named by the title passed to OpenAsync, or by DialogOptions.Label without one; ConfirmAsync without a title is named by its message.

Parameters

Dialog 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.Center
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

  • Drawer: a dialog that slides in from an edge.
  • ConfirmButton: asks in an alert dialog before acting.
  • Popover: non-modal content beside a trigger.
  • Alert: a status message about part of a page.