Dialog
JavaScript off: what the HTML and CSS do on their own.
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;
}Off: The trigger opens it as a modal. The link loads the page with it open but not modal, so focus can leave it.
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; }
}
}Off: Rename posts and loads the page again. A slash, which only the server checks, opens the dialog again with its message.
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
This example runs C# over a Blazor Server connection, which an Off page doesn't have. Show this page Live
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.