Dialog
A static page with IzzyBlazor's script. No circuit.
Usage
<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
Code
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>
{
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
Code
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>
{
[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.
Header, body and footer
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
Code
<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>
{
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
Code
<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.
<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
Code
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>
{
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:
DialogService Dialogs
{
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:
<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>
{
[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
| Feature | Off | Static | Live |
|---|---|---|---|
| Everything | Partly | Yes | Yes |
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(anh2) or a DialogHeader (a level-2 heading) names the dialog witharia-labelledby. Without either it has no name: pass anaria-label.- The close button is named "Close" (
CloseLabel). Escape closes the dialog even whenShowCloseButtonandCloseOnBackdropare off. - DialogTrigger is a native
buttonwitharia-haspopup="dialog", and focus comes back to it when the dialog closes. - Put
autofocuson 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 theclosedbyattribute (Chrome and Edge 134, Firefox 141; not Safari), Escape closes it, and so does a click outside whileCloseOnBackdropis on. DialogTrigger needs no script in current browsers. - DialogService's dialogs (shown by DialogHost) are named by the title passed to
OpenAsync, or byDialogOptions.Labelwithout one;ConfirmAsyncwithout a title is named by its message.
Parameters
| Prop | Type | Default |
|---|---|---|
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. | DialogSize | DialogSize.Md |
PlacementCenter (the default), a full-height drawer on the start or end side, or a full-width sheet along the top or bottom. | DialogPlacement | DialogPlacement.Center |
CloseOnBackdropCloses when the backdrop is clicked. | bool | true |
ShowCloseButtonShows the close button in the header. | bool | true |
InsideFormSet when the dialog is inside a <form>. | bool | false |
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. | bool | false |
IdThe element id. | string? |
Plus 16 item props and 4 common props.
Full APISee 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.