Skip to content
IzzyBlazor 0.1.0-alpha.17

Enter all results ↑ ↓ move Esc close

Select

A native select, themed where the browser allows.

JavaScript off: what the HTML and CSS do on their own.

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 }
}

Off: A browser without the customizable select shows each option's text run together ("Pro$12 a month"); the script puts ItemText in its place.

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 }
}

Off: The browser stops the post until a plan is chosen, in its own words.

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.