Form
JavaScript off: what the HTML and CSS do on their own.
Usage
<Form Model="Profile" FormName="profile" OnValidSubmit="SaveAsync">
<FormErrors />
<TextField @bind-Value="Profile!.Name" />
<TextField @bind-Value="Profile!.Email" />
<FormActions><Button Submit>Save</Button></FormActions>
</Form>
{
// A post on a static page fills it; the same name as FormName.
[SupplyParameterFromForm(FormName = "profile")]
private ProfileModel? Profile { get; set; }
protected override void OnInitialized() => Profile ??= new();
}Fields
Bind each field to the model with @bind-Value. Fields take their labels, required marks, types and limits from the model's attributes, and post with the form.
Preview
Code
System.ComponentModel.DataAnnotations
<Form Model="Model" FormName="workspace" OnValidSubmit="Save" Grow>
<Heading Level="2" Size="TextSize.Lg">Workspace</Heading>
<TextField @bind-Value="Model!.Name" />
<Row Gap="4" Wrap>
<Select @bind-Value="Model!.Region" Grow />
<SegmentedControl @bind-Value="Model!.Theme" />
</Row>
<Slider @bind-Value="Model!.Timeout" Min="5" Max="120" Step="5" ShowValue ValueFormat="{0} min" />
<Switch @bind-Value="Model!.Alerts" ControlPosition="ControlPosition.End" Hint="An email when a deploy fails." />
<FormActions><Button Submit>Save</Button></FormActions>
if (_saved)
{
<Alert Status="Status.Success">Saved Model!.Name: Model.Theme, Model.Timeout-minute sessions.</Alert>
}
</Form>
{
[SupplyParameterFromForm(FormName = "workspace")]
private Workspace? Model { get; set; }
private bool _saved;
protected override void OnInitialized() => Model ??= new();
private void Save() => _saved = true;
public sealed class Workspace
{
[Required(ErrorMessage = "Name the workspace"), StringLength(40)]
public string Name { get; set; } = "Acme";
public Region Region { get; set; } = Region.UsEast;
public Theme Theme { get; set; } = Theme.Dark;
[Display(Name = "Session timeout")]
public int Timeout { get; set; } = 30;
[Display(Name = "Deploy alerts")]
public bool Alerts { get; set; } = true;
}
public enum Region
{
[Display(Name = "US East")] UsEast,
[Display(Name = "US West")] UsWest,
[Display(Name = "Europe")] Europe,
[Display(Name = "Asia Pacific")] AsiaPacific,
}
public enum Theme { Light, Dark, System }
}Off: Every control still works, and Save posts, checks and shows the result.
Forms and validation lists what each attribute does.
Posting and submitting
Give the form a FormName, and the model property [SupplyParameterFromForm] with the same name, so a post on a static page fills it (what goes wrong without it). OnValidSubmit runs once the model is valid, in every mode; OnInvalidSubmit runs when it isn't, and OnSubmit runs in place of both when you validate yourself.
On a static page the post is a real one: Blazor's enhanced form handling sends it without a full reload, and with JavaScript off the browser posts and loads the page. Enhance="false" always loads the page. On an interactive page the form submits to C#, with no post.
Errors
Use a FormErrors at the top to list every message after a failed submit; it takes focus, so keyboard and screen reader users start there. NativeValidation="false" skips the browser's checks, so the server's messages show at once.
Preview
Code
System.ComponentModel.DataAnnotations
@* NativeValidation="false" skips the browser's checks, so the server's messages show at once. *@
<Form Model="Model" FormName="feedback" OnValidSubmit="Send" NativeValidation="false" MaxWidth="28.Rem" Grow>
<FormErrors />
<TextField @bind-Value="Model!.Email" AutoComplete="email" />
<TextArea @bind-Value="Model!.Message" Rows="3" />
<FormActions Justify="Justify.Start"><Button Submit>Send</Button></FormActions>
if (_sent)
{
<Alert Status="Status.Success">Thanks, we'll reply to Model!.Email.</Alert>
}
</Form>
{
[SupplyParameterFromForm(FormName = "feedback")]
private Feedback? Model { get; set; }
private bool _sent;
protected override void OnInitialized() => Model ??= new();
private void Send() => _sent = true;
public sealed class Feedback
{
[Required(ErrorMessage = "Enter your email"), EmailAddress(ErrorMessage = "Enter an email like name@example.com")]
public string? Email { get; set; }
[Required(ErrorMessage = "Write a message"), StringLength(500, MinimumLength = 10, ErrorMessage = "Use 10 to 500 characters")]
public string? Message { get; set; }
}
}Off: Send posts and loads the page again with the messages, and the summary takes focus.
Each field shows its own messages under it too. FieldError For="() => Model.Email" Id="email-error" shows one property's messages beside an input of your own, whose aria-describedby names its Id.
Columns
Use Columns, up to 4, to lay fields out in columns that fit the form's own width, so one form works on a page, in a dialog and in a drawer. ColSpan="GridSpan.Full" gives a field the whole row.
Preview
Code
System.ComponentModel.DataAnnotations
@* A two-column form: labels and rules from the model, a field across the row, the errors listed
at the top after a failed submit, and the buttons at the end. *@
<Form Model="Model" FormName="contact" Columns="2" OnValidSubmit="Save" Grow>
<FormErrors />
<TextField @bind-Value="Model!.FirstName" AutoComplete="given-name" />
<TextField @bind-Value="Model!.LastName" AutoComplete="family-name" />
<TextField @bind-Value="Model!.Email" AutoComplete="email" ColSpan="GridSpan.Full" />
<TextField @bind-Value="Model!.City" AutoComplete="address-level2" />
<Select @bind-Value="Model!.Country" />
<FormActions><Button Submit>Save contact</Button></FormActions>
if (_saved)
{
<Alert Status="Status.Success">Saved Model!.FirstName Model.LastName.</Alert>
}
</Form>
{
[SupplyParameterFromForm(FormName = "contact")]
private Contact? Model { get; set; }
private bool _saved;
protected override void OnInitialized() => Model ??= new();
private void Save() => _saved = true;
public sealed class Contact
{
[Required(ErrorMessage = "Enter a first name")]
public string? FirstName { get; set; }
[Required(ErrorMessage = "Enter a last name")]
public string? LastName { get; set; }
[Required(ErrorMessage = "Enter an email"), EmailAddress(ErrorMessage = "Enter an email like name@example.com")]
public string? Email { get; set; }
public string? City { get; set; }
public Country Country { get; set; } = Country.UnitedStates;
}
public enum Country
{
[Display(Name = "United States")] UnitedStates,
Canada,
Mexico,
[Display(Name = "United Kingdom")] UnitedKingdom,
}
}Off: The browser checks before posting, in its own words.
Two columns start at 32rem, three at 48rem and four at 64rem. FormActions, FormErrors and anything else that isn't a field take the whole row.
Labels beside fields
Use Layout="FormLayout.Horizontal" to put the labels beside the fields from 640px up, and RequiredMark for the marks: an asterisk on required fields (the default), "(optional)" on the others, as here, or none.
Preview
Code
System.ComponentModel.DataAnnotations
<Form Model="Model" FormName="profile" Layout="FormLayout.Horizontal" RequiredMark="RequiredMark.Optional" Grow>
<TextField @bind-Value="Model!.Name" AutoComplete="name" />
<TextField @bind-Value="Model!.Email" AutoComplete="email" />
<TextField @bind-Value="Model!.Website" AutoComplete="url" />
<FormActions><Button Submit>Save profile</Button></FormActions>
</Form>
{
[SupplyParameterFromForm(FormName = "profile")]
private Profile? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
public sealed class Profile
{
[Required]
public string? Name { get; set; } = "Ada Lovelace";
[Required, EmailAddress]
public string? Email { get; set; } = "ada@example.com";
[Url]
public string? Website { get; set; }
}
}Read-only and disabled
Use ReadOnly to show a record that posts but can't change, and Disabled to turn every field off; disabled fields don't post. Neither touches the buttons, so show the ones that fit.
Preview
Code
System.ComponentModel.DataAnnotations
<Form Model="Model" FormName="member" ReadOnly Columns="2" Grow>
<TextField @bind-Value="Model!.Name" />
<Select @bind-Value="Model!.Role" />
<Checkbox @bind-Value="Model!.Billing" Label="Can see billing" ColSpan="GridSpan.Full" />
<Switch @bind-Value="Model!.Active" Label="Active" ColSpan="GridSpan.Full" />
</Form>
{
[SupplyParameterFromForm(FormName = "member")]
private Member? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
public sealed class Member
{
public string? Name { get; set; } = "Grace Hopper";
public Role Role { get; set; } = Role.Editor;
public bool Billing { get; set; } = true;
public bool Active { get; set; } = true;
}
public enum Role { Viewer, Editor, Owner }
}HTML has no read-only select, checkbox, radio or range, so a read-only select shows its value in a read-only text field, and checkboxes, switches, radios, segments and sliders are disabled; hidden fields post their values, which a crafted post can change like any other, so check them on the server if it matters.
Your own validation
Use Validator to replace the DataAnnotations validator, with FluentValidation, say. Pass an EditContext in place of Model to work with the edit context yourself.
@* Blazored.FluentValidation's validator, in place of DataAnnotations. *@
<Form Model="Order" FormName="order" OnValidSubmit="PlaceAsync">
<Validator><FluentValidationValidator /></Validator>
...
</Form>Files
Use Multipart with a FileUpload, so a post on a static page sends the files and not only their names.
<Form Model="Profile" FormName="profile" Multipart OnValidSubmit="SaveAsync">
<FileUpload Name="Avatar" Label="Profile photo" Accept=".png,.jpg" MaxFileSize="2_000_000" />
<FormActions><Button Submit>Save</Button></FormActions>
</Form>
{
[SupplyParameterFromForm(FormName = "profile")]
private ProfileModel? Profile { get; set; }
// The file, named like the field.
[SupplyParameterFromForm(FormName = "profile")]
private IFormFile? Avatar { get; set; }
protected override void OnInitialized() => Profile ??= new();
}Parts
Written inside Form. The API page lists their parameters.
FormActions: the row of buttons at the end of a form.FormErrors: a summary of every validation message in the form.
Mode support
| Feature | Off | Static | Live |
|---|---|---|---|
| Everything | Yes | Yes | Yes |
Off: JavaScript disabled. Static: a static SSR page with IzzyBlazor's script. Live: an interactive page (Server, WebAssembly or Auto).
Accessibility
- It renders a native
form. WithNativeValidation(the default), the browser stops a submit with a missing or malformed value, focuses that field and shows its own message. - Each field links its errors with
aria-describedbyand setsaria-invalid; errors aren't announced as they appear. - Place a
FormErrorsat the top: it takes focus after a submit that fails validation, with JavaScript off too, and screen readers read its title and messages then (it isn't also a live region, which would read it twice). Form moves no focus itself. On static pages a CheckboxGroup, RadioGroup or SegmentedControl with an error takes focus after the post too (its error belongs to the group, so no one box reads it); whichever of these comes first on the page keeps it. - The summary's messages are plain text, not links to their fields.
RequiredMarkchanges only what's shown: the asterisk is hidden from screen readers, which hear "required" from the control, and "(optional)" is read with the label.Disabledfields can't be focused. WithReadOnly, text fields stay focusable and are announced as read only; checkboxes, switches, radios, segments and sliders are disabled.
Parameters
| Prop | Type | Default |
|---|---|---|
ModelThe object the form edits. | object? | |
EditContextAn explicit edit context. | EditContext? | |
FormNameThe form's name. | string? | |
MultipartPosts as multipart/form-data, which a FileUpload needs to send its files on static pages. | bool | false |
EnhanceUses Blazor's enhanced form handling on static pages (no full page reload). | bool | true |
NativeValidationLets the browser check required fields, lengths and formats before posting. | bool | true |
ValidatorReplaces the default DataAnnotations validator, for example with a FluentValidation one. | RenderFragment? | |
LayoutHow labels sit next to fields. | FormLayout | FormLayout.Vertical |
GapSpace between fields, in spacing steps. | Responsive<int> | 5 |
ColumnsLays the fields out in up to this many columns (1 to 4). | int | 1 |
DisabledDisables every field in the form. | bool | false |
ReadOnlyMakes every field read-only: shown and posted, but not changed, as on a page that shows a record before editing it. | bool | false |
RequiredMarkWhich fields are marked beside their labels: RequiredMark.Required (an asterisk on required fields, the default), RequiredMark.Optional ("(optional)" on the others) or RequiredMark.None. | RequiredMark | RequiredMark.Required |
ChildContentThe fields and buttons. | RenderFragment? | |
OnValidSubmitRuns on the server after a submit that passes validation. | EventCallback<EditContext> | |
OnInvalidSubmitRuns on the server after a submit that fails validation. | EventCallback<EditContext> | |
OnSubmitRuns on every submit, instead of the valid and invalid callbacks. | EventCallback<EditContext> |
Plus 16 item props and 4 common props.
Full APISee also
- Fieldset: groups related fields under a legend.
- TextField: a labeled text input with add-ons and a password reveal.
- Button: submits, resets, links or runs C#.
- Alert: a status message about part of a page.
- Demos: Sign-up form.
- Troubleshooting: An empty selection comes back with its defaults; A form's post fails or arrives empty; Code runs after NavigateTo, or the post doesn't redirect; A form posts to [object RadioNodeList].