Skip to content
IzzyBlazor 0.1.0-alpha.17

Enter all results ↑ ↓ move Esc close

Forms and validation

Form wraps Blazor's EditForm, so one form works everywhere: a real post on a static page, even with JavaScript off, and live validation on an interactive one. Fields take their labels and rules from your model.

A form that posts

Give the form a FormName, and the model property [SupplyParameterFromForm] with the same FormName, so the post fills it on a static page:

razor
<Form Model="Signup" FormName="signup" OnValidSubmit="SaveAsync">
    <FormErrors />
    <TextField @bind-Value="Signup.Email" />
    <Select @bind-Value="Signup.Plan" Placeholder="Choose a plan" />
    <Button Submit>Create account</Button>
</Form>

@code {
    [SupplyParameterFromForm(FormName = "signup")]
    public SignupModel? Signup { get; set; }

    protected override void OnInitialized() => Signup ??= new();
}

OnValidSubmit runs on the server once the model is valid, in every mode. On an interactive page the same form checks each field as it changes and submits without a page load. On a static page the errors come from the last post: once a field's value changes its errors go (in a Combobox or MultiSelect, when an option is chosen, not as the search is typed), and the next post checks it again. A FormErrors summary keeps listing them, and with JavaScript off they all stay until the next post.

Labels and rules from the model

Each field reads the property's attributes: its label from [Display(Name)] (or the property name in words), the required mark from [Required], the input type from [EmailAddress], [Phone] or [Url], and limits from [StringLength] and [Range].

csharp
public sealed class SignupModel
{
    [Required(ErrorMessage = "Enter your email"), EmailAddress]
    public string? Email { get; set; }      // "Email", type="email", required

    [StringLength(10)]
    public string? ZipCode { get; set; }    // "Zip code", maxlength="10"

    [Required(ErrorMessage = "Choose a plan")]
    public Plan? Plan { get; set; }         // a select of the enum's values
}

Without [Display(Name)], the label splits the property name into words ("Zip code"), but validation messages use it as written ("The ZipCode field is required."). Add [Display(Name)] when the messages should read like the label.

Checked before and after posting

The browser checks required fields, lengths, ranges, emails and URLs before posting. With IzzyBlazor's script it uses your attribute's ErrorMessage, so it says what the server would; without the script, the browser uses its own words. The server checks every rule after posting and shows its messages under each field. Rules the browser can't check, such as a custom attribute, run only on the server.

Required on a field marks it and asks the browser to check it, but the server only reads [Required] on the model: put rules that must hold there.

Columns

Use Columns, up to 4, to lay fields out in columns sized by the form's own width, so one form fits a page, a dialog and a drawer. Each field takes a column; ColSpan="GridSpan.Full" gives it the row.

Preview

Code

Columns.razor
@using 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>

@code {
    [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,
    }
}

Static: The browser checks before posting, in the model's words.

Two columns start at 32rem, three at 48rem and four at 64rem; below that it's one. Anything that isn't a field, such as text, FormErrors or FormActions, takes the whole row. NewRow starts a field on a new row.

Groups, actions and errors

  • Fieldset groups related fields under a Label, with its own Columns or Dividers between rows.
  • FormActions is the row of buttons at the end. Last in a form that fills a dialog or drawer, it becomes the footer and stays in view.
  • FormErrors lists every message under "Please fix the following", and takes focus after a failed submit, with JavaScript off too.
  • FieldError For="() => Model.Email" Id="email-error" shows one property's messages, for an input of your own; point the input's aria-describedby at its Id.

Read-only and disabled

ReadOnly on the form makes every field read-only: the values show and post, but don't change, as on a page that shows a record before editing it. HTML has no read-only select, checkbox, radio or range, so selects show their value in a read-only text field, and checkboxes, switches, radios, segments and sliders are disabled while hidden fields post their values.

Disabled on the form disables every field, and disabled fields post nothing. Neither touches buttons, so show the ones that fit. A field's own Disabled works the same way, and so does its own ReadOnly on the fields that have one (text fields and selects; checkboxes, switches, radios, segments and sliders don't).

Labels and marks

RequiredMark on the form picks the marks: an asterisk on required fields (the default), "(optional)" on the others, which reads better when most fields are required, or none. Layout="FormLayout.Horizontal" puts labels beside the fields, at the start, from 640px up; with Columns they stay on top. HideLabel keeps a field's label for screen readers only, as in a one-field search form.

Next steps

  • Form: every parameter, and Validator to replace DataAnnotations.
  • TextField: types, add-ons, passwords and validation.
  • Text and other languages: the words forms use, such as "(optional)".
  • Troubleshooting: a post that fails or arrives empty, an empty selection, a redirect after saving.