Skip to content
IzzyBlazor 0.1.0-alpha.17

Enter all results ↑ ↓ move Esc close

Select

A native select, themed where the browser allows.

An interactive page: C# runs over a Blazor Server connection.

Usage

razor
@* An enum: its values are the options. *@
<Select @bind-Value="Model!.Plan" />

@* Your own list, with the text for each. *@
<Select @bind-Value="Model!.Country" Items="CountryCodes" ItemText="code => CountryNames[code!]"
        Placeholder="Choose a country" />

Options

Bind an enum and its values are the options, named by their [Display(Name)]. For anything else, pass Items and the ItemText for each. Placeholder adds a first, empty option.

Preview

Code

Options.razor
@using System.ComponentModel.DataAnnotations

<Form Model="Model" FormName="options" MaxWidth="24.Rem" Grow>
    @* An enum: its values are the options, named by [Display(Name)]. *@
    <Select @bind-Value="Model!.Plan" />
    @* A list of your own, with the text for each. *@
    <Select @bind-Value="Model!.Country" Items="Countries.Keys" ItemText="code => Countries[code!]"
            Placeholder="Choose a country" />
</Form>

@code {
    [SupplyParameterFromForm(FormName = "options")]
    private Signup? Model { get; set; }

    private static readonly Dictionary<string, string> Countries = new()
    {
        ["CA"] = "Canada", ["MX"] = "Mexico", ["GB"] = "United Kingdom", ["US"] = "United States",
    };

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

    public sealed class Signup
    {
        public Plan Plan { get; set; } = Plan.Pro;

        // A country code: "CA", "MX", ...
        public string? Country { get; set; }
    }

    public enum Plan
    {
        Free,
        Pro,
        [Display(Name = "Team (5 seats)")] Team,
    }
}

The label and required mark come from the model as they do for a TextField: [Display(Name)] or the property name in words, and [Required]. Label replaces the label, and Hint adds help under the field.

Rich options

Use ItemTemplate for markup in each option, such as an icon and a price. Text with the class ib-option-hint shows in the list but not in the closed field.

Preview

Code

Rich.razor
<Form Model="Model" FormName="rich" MaxWidth="24.Rem" Grow>
    <Select @bind-Value="Model!.Plan" ItemText="plan => plan.ToString()">
        <ItemTemplate>
            <Icon Name="@Icons[context]" />
            <span>@context</span>
            <small class="ib-option-hint">@Prices[context]</small>
        </ItemTemplate>
    </Select>
</Form>

@code {
    [SupplyParameterFromForm(FormName = "rich")]
    private Signup? Model { get; set; }

    private static readonly Dictionary<Plan, IconData> Icons = new()
    {
        [Plan.Free] = Lucide.Leaf, [Plan.Pro] = Lucide.Rocket, [Plan.Team] = Lucide.Users,
    };

    private static readonly Dictionary<Plan, string> Prices = new()
    {
        [Plan.Free] = "$0", [Plan.Pro] = "$12 a month", [Plan.Team] = "$40 a month",
    };

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

    public sealed class Signup
    {
        public Plan Plan { get; set; } = Plan.Pro;
    }

    public enum Plan { Free, Pro, Team }
}

The markup shows where the browser has the customizable select (Chrome and Edge 135, Safari 27). Elsewhere IzzyBlazor's script shows ItemText in the browser's own list, so keep both; with JavaScript off those browsers show the markup's text run together.

Required, with a placeholder

Use a nullable property with [Required] and a Placeholder: nothing is chosen until someone picks an option, and the placeholder can't be picked again.

Preview

Code

Required.razor
@using System.ComponentModel.DataAnnotations

<Form Model="Model" FormName="required" OnValidSubmit="Save" MaxWidth="24.Rem" Grow>
    <Select @bind-Value="Model!.Plan" Placeholder="Choose a plan" />
    <Row><Button Submit>Continue</Button></Row>
    @if (_saved)
    {
        <Alert Status="Status.Success">You chose @Model!.Plan.</Alert>
    }
</Form>

@code {
    [SupplyParameterFromForm(FormName = "required")]
    private Signup? Model { get; set; }

    private bool _saved;

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

    private void Save() => _saved = true;

    public sealed class Signup
    {
        // Nullable, so nothing is chosen until someone picks one.
        [Required(ErrorMessage = "Choose a plan")]
        public Plan? Plan { get; set; }
    }

    public enum Plan { Free, Pro, Team }
}

Live: The browser stops the post in the model's words, as on static pages, and Continue runs C# without a page load.

Sizes, read-only and disabled

Use Size to line up with buttons and other fields, ReadOnly for a value that posts but can't change, and Disabled for one that doesn't post.

Preview

Read-only: posted, but can't be changed.

Disabled: not posted.

Code

States.razor
<Form Model="Model" FormName="select-states" MaxWidth="24.Rem" Grow>
    <Select @bind-Value="Model!.Small" Label="Small" Size="ControlSize.Sm" />
    <Select @bind-Value="Model!.Large" Label="Large" Size="ControlSize.Lg" />
    <Select @bind-Value="Model!.Region" Label="Region" ReadOnly Hint="Read-only: posted, but can't be changed." />
    <Select @bind-Value="Model!.Backup" Label="Backup region" Disabled Hint="Disabled: not posted." />
</Form>

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

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

    public sealed class Settings
    {
        public Region Small { get; set; }
        public Region Large { get; set; }
        public Region Region { get; set; } = Region.Europe;
        public Region Backup { get; set; } = Region.Asia;
    }

    public enum Region { Americas, Europe, Asia }
}

HTML has no read-only select, so a read-only one shows its choice in a read-only text field and posts it from a hidden input.

Filtering a page

Use a FilterSelect in a FilterForm to filter a list as soon as someone picks, since a select doesn't post when it changes: it applies on change with the script and has an Apply button without it. On an interactive page, @bind-Value:after runs C# after each change.

razor
@* Any page: sets ?status= *@
<FilterForm>
    <FilterSelect Name="status" Label="Status" EnumType="typeof(OrderStatus)" />
</FilterForm>

@* Interactive page: C# after each change. *@
<Select @bind-Value="Model!.Status" @bind-Value:after="ReloadAsync" />

Mode support

What works in each mode, Select
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 a native select with a label linked to it, so the keyboard, typing to jump to an option and the screen reader's announcements are the browser's own.
  • The hint and, when the field is invalid, its errors are linked with aria-describedby; an invalid field sets aria-invalid.
  • A read-only Select shows its value in a read-only text field, which can be focused and is announced as read-only, where a disabled select would be skipped.
  • The chosen text's hover tooltip is on the wrapper, not the select, so screen readers don't hear the value twice.
  • With ItemTemplate, keep ItemText: browsers without the customizable select show and read it in place of the markup.

Parameters

Select parameters
PropTypeDefault
ReadOnlyMakes the field read-only: the chosen text shows in a read-only text field, and the value still posts.boolfalse
ItemsThe options.IEnumerable<TValue>?
ItemTextThe text for each option.Func<TValue, string>?computed
ItemTemplateMarkup for each option, such as an icon, avatar or a second line (class ib-option-hint, hidden in the closed field).RenderFragment<TValue>?
PlaceholderA first, empty option such as "Choose a plan".string?
@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

  • Combobox: type to filter; server search on interactive pages.
  • RadioGroup: one choice from a list of radios.
  • SegmentedControl: one choice from a few, as joined buttons.
  • MultiSelect: several choices as chips you can type to filter.