Skip to content
IzzyBlazor 0.1.0-alpha.17

Enter all results ↑ ↓ move Esc close

RadioGroup

One choice from a list of radios.

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

Usage

razor
@* An enum: its values are the options, named by their [Display(Name)]. *@
<RadioGroup @bind-Value="Model!.Digest" />

@* Other values, with ItemText for each one's text. *@
<RadioGroup @bind-Value="Model!.Rate" Items="Rates.Keys" ItemText="RateText"
            Orientation="Orientation.Horizontal" />

For two to five short options, a SegmentedControl takes less room; for a long list, use a Select.

Stacked or in a row

Use Orientation.Horizontal to put a few short options in a row, wrapping when there isn't room; they stack by default. An enum's values are the options, named by their [Display(Name)].

Preview

Email digest

A summary of your projects' activity, by email.

Preferred contact

Code

Horizontal.razor
@using System.ComponentModel.DataAnnotations

<Form Model="Model" FormName="radio-group-horizontal" MaxWidth="28.Rem" Grow>
    <RadioGroup @bind-Value="Model!.Digest" Hint="A summary of your projects' activity, by email." />
    <RadioGroup @bind-Value="Model!.Contact" Orientation="Orientation.Horizontal" />
</Form>

@code {
    [SupplyParameterFromForm(FormName = "radio-group-horizontal")]
    private Preferences? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    public sealed class Preferences
    {
        [Display(Name = "Email digest")]
        public Digest Digest { get; set; } = Digest.Weekly;

        [Display(Name = "Preferred contact")]
        public ContactMethod Contact { get; set; } = ContactMethod.Email;
    }

    public enum Digest { Daily, Weekly, Never }

    public enum ContactMethod
    {
        Email,
        Phone,
        [Display(Name = "Don't contact me")] None,
    }
}

Options from a list

Use Items for options that aren't an enum, and ItemText for how each one reads.

Preview

Shipping

Code

Shipping.razor
@using System.ComponentModel.DataAnnotations

<Form Model="Model" FormName="radio-group-shipping" MaxWidth="24.Rem" Grow>
    <RadioGroup @bind-Value="Model!.Rate" Items="Rates.Keys" ItemText="RateText" />
</Form>

@code {
    [SupplyParameterFromForm(FormName = "radio-group-shipping")]
    private Checkout? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private static readonly Dictionary<string, (string Name, decimal Price)> Rates = new()
    {
        ["standard"] = ("Standard, 3 to 5 days", 4.99m),
        ["express"] = ("Express, 1 to 2 days", 12.99m),
        ["pickup"] = ("Pick up in store", 0m),
    };

    private static string RateText(string? key)
    {
        var (name, price) = Rates[key!];
        return price == 0 ? $"{name}: free" : $"{name}: ${price}";
    }

    public sealed class Checkout
    {
        [Display(Name = "Shipping")]
        public string? Rate { get; set; } = "standard";
    }
}

Each radio posts its value as text and Blazor turns it back into the property's type, so use values that do that, such as strings, numbers or enums. A post can carry a value that isn't among the options, and it reaches the model, so check it on the server.

Required, with nothing chosen

Bind a nullable property with [Required] to start with nothing chosen and need a choice.

Preview

How did you hear about us?

Code

RequiredChoice.razor
@using System.ComponentModel.DataAnnotations

<Form Model="Model" FormName="radio-group-required-choice" OnValidSubmit="Save" MaxWidth="28.Rem" Grow>
    <RadioGroup @bind-Value="Model!.Source" />
    <Row><Button Submit>Send</Button></Row>
    @if (_sent is not null)
    {
        <Alert Status="Status.Success">Thanks: you heard about us from @_sent.</Alert>
    }
    else if (_unknown)
    {
        <Alert Status="Status.Danger">Choose one of the options.</Alert>
    }
</Form>

@code {
    [SupplyParameterFromForm(FormName = "radio-group-required-choice")]
    private Survey? Model { get; set; }

    private string? _sent;
    private bool _unknown;

    protected override void OnInitialized() => Model ??= new();

    // What was sent, not the field: on an interactive page it changes as people choose. A post
    // can carry any number for an enum, so anything else is turned away.
    private void Save()
    {
        _sent = Model!.Source switch
        {
            Source.Friend => "a friend or colleague",
            Source.Search => "a search",
            Source.Social => "social media",
            Source.Talk => "a talk",
            _ => null,
        };
        _unknown = _sent is null;
    }

    public sealed class Survey
    {
        // Nullable, so nothing is chosen until someone chooses.
        [Required(ErrorMessage = "Choose how you heard about us")]
        [Display(Name = "How did you hear about us?")]
        public Source? Source { get; set; }
    }

    public enum Source
    {
        [Display(Name = "A friend or colleague")] Friend,
        Search,
        [Display(Name = "Social media")] Social,
        [Display(Name = "A conference talk")] Talk,
    }
}

Static: The browser stops the post until an option is chosen, in the model's words.

A property that isn't nullable always has a value, so [Required] never fails: the option for its starting value (an enum's 0, unless the model sets another) is chosen from the start. The example also turns away a posted number that isn't one of the enum's values.

Sizes and disabled

Use Size to line up with buttons and other fields, and Disabled for a choice that can't change, which doesn't post. There's no ReadOnly: HTML has no read-only radio, and in a read-only Form the group is disabled while a hidden field posts its value.

Preview

Small
Large
Region

Disabled: not posted.

Code

States.razor
<Form Model="Model" FormName="radio-group-states" MaxWidth="32.Rem" Grow>
    <RadioGroup @bind-Value="Model!.Small" Label="Small" Size="ControlSize.Sm" Orientation="Orientation.Horizontal" />
    <RadioGroup @bind-Value="Model!.Large" Label="Large" Size="ControlSize.Lg" Orientation="Orientation.Horizontal" />
    <RadioGroup @bind-Value="Model!.Region" Label="Region" Disabled Orientation="Orientation.Horizontal"
                Hint="Disabled: not posted." />
</Form>

@code {
    [SupplyParameterFromForm(FormName = "radio-group-states")]
    private Account? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    public sealed class Account
    {
        public Priority Small { get; set; } = Priority.Normal;
        public Priority Large { get; set; } = Priority.Normal;
        public Region Region { get; set; } = Region.Europe;
    }

    public enum Priority { Low, Normal, High }

    public enum Region { Americas, Europe, Asia }
}

A disabled field posts nothing, so after a static post its property holds its default (an enum's 0, or null): keep its value on the server rather than saving it from the post.

Mode support

What works in each mode, RadioGroup
FeatureOffStaticLive
EverythingYesYesYes

Off: JavaScript disabled. Static: a static SSR page with IzzyBlazor's script. Live: an interactive page (Server, WebAssembly or Auto).

Accessibility

  • It renders native radio buttons in a fieldset whose legend is the label, so Tab reaches the group and the arrow keys move the choice.
  • Each option's text is its label; the hint and errors are linked to the group with aria-describedby, and each radio sets aria-invalid when the field is invalid.
  • After a post that fails with an error here, the checked radio (or the first) takes focus, so screen readers read the group's name and error (its description); a FormErrors earlier in the page takes it first. Not on interactive pages: there, use a FormErrors.

Parameters

RadioGroup parameters
PropTypeDefault
ItemsThe options.IEnumerable<TValue>?
ItemTextThe text for each option.Func<TValue, string>?computed
OrientationStack options vertically (the default) or in a row.OrientationOrientation.Vertical
@bind-ValueThe value, bound with @bind-Value.TValue
DisplayNameThe field's name in validation messages from the input itself, such as a value that can't be parsed.string?

See also