Select
A native <select> wrapper with built-in sizing, semantic token styling, and full Tailwind
override support. Supports two-way binding, placeholder, disabled options, form validation, and arbitrary HTML attribute passthrough.
Overview
MSelect renders a styled native <select> that matches MInput
in sizing and visual language out of the box. Use Size to control height, padding, and
font size. Pass Class for safe Tailwind overrides — user-supplied classes always win
conflicts. The component handles single-value binding, placeholder option rendering, disabled state,
disabled per-option, typed option data, and arbitrary HTML attribute forwarding.
Usage
Add the namespace import and include the stylesheet reference if not already done.
@using Marai.UI.Components.MSelectBasic Usage
<div class="relative max-w-xs">
<MSelect Items="_fruits"
Placeholder="Pick a fruit"
Class="w-full rounded-md border border-slate-300 bg-white pl-3 pr-10 py-2 text-sm text-slate-900 appearance-none focus:outline-none focus:ring-2 focus:ring-sky-500" />
<div class="pointer-events-none absolute top-1/2 -translate-y-1/2 right-3">
<svg class="h-4 w-4 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</div>
</div>
@code {
private static readonly MSelectOption[] _fruits =
[
new("apple", "Apple"),
new("banana", "Banana"),
new("cherry", "Cherry"),
new("mango", "Mango"),
];
}
Examples
Placeholder
Set Placeholder to render an initial disabled empty option prompting the user to pick a value.
<div class="relative max-w-xs">
<MSelect Items="_fruits"
Placeholder="Select a fruit..."
Class="w-full rounded-md border border-slate-300 bg-white pl-3 pr-10 py-2 text-sm text-slate-900 appearance-none focus:outline-none focus:ring-2 focus:ring-sky-500" />
<div class="pointer-events-none absolute top-1/2 -translate-y-1/2 right-3">
<svg class="h-4 w-4 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</div>
</div>
@code {
private static readonly MSelectOption[] _fruits =
[
new("apple", "Apple"),
new("banana", "Banana"),
new("cherry", "Cherry"),
new("mango", "Mango"),
];
}
Default Selected
Supply a Value to pre-select an option on first render.
<div class="relative max-w-xs">
<MSelect Items="_fruits"
Value="banana"
Class="w-full rounded-md border border-slate-300 bg-white pl-3 pr-10 py-2 text-sm text-slate-900 appearance-none focus:outline-none focus:ring-2 focus:ring-sky-500" />
<div class="pointer-events-none absolute top-1/2 -translate-y-1/2 right-3">
<svg class="h-4 w-4 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</div>
</div>
@code {
private static readonly MSelectOption[] _fruits =
[
new("apple", "Apple"),
new("banana", "Banana"),
new("cherry", "Cherry"),
new("mango", "Mango"),
];
}
Two-Way Binding
Use @bind-Value to keep a field in sync with the selected option.
Selected: none
<div class="flex flex-col gap-3 max-w-xs">
<div class="relative">
<MSelect Items="_fruits"
@bind-Value="_selected"
Placeholder="Choose a fruit"
Class="w-full rounded-md border border-slate-300 bg-white pl-3 pr-10 py-2 text-sm text-slate-900 appearance-none focus:outline-none focus:ring-2 focus:ring-sky-500" />
<div class="pointer-events-none absolute top-1/2 -translate-y-1/2 right-3">
<svg class="h-4 w-4 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</div>
</div>
<p class="text-sm text-muted-foreground">Selected: @(_selected ?? "none")</p>
</div>
@code {
private string? _selected;
private static readonly MSelectOption[] _fruits =
[
new("apple", "Apple"),
new("banana", "Banana"),
new("cherry", "Cherry"),
new("mango", "Mango"),
];
}
Disabled
Pass Disabled="true" to set the native disabled attribute. Apply disabled visual styles (e.g. disabled:cursor-not-allowed disabled:opacity-50) in your Class string.
<div class="relative max-w-xs">
<MSelect Items="_fruits"
Value="apple"
Disabled="true"
Class="w-full rounded-md border border-slate-300 bg-white pl-3 pr-10 py-2 text-sm text-slate-900 appearance-none focus:outline-none focus:ring-2 focus:ring-sky-500 disabled:cursor-not-allowed disabled:opacity-50" />
<div class="pointer-events-none absolute top-1/2 -translate-y-1/2 right-3 opacity-40">
<svg class="h-4 w-4 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</div>
</div>
@code {
private static readonly MSelectOption[] _fruits =
[
new("apple", "Apple"),
new("banana", "Banana"),
new("cherry", "Cherry"),
new("mango", "Mango"),
];
}
Disabled Option
Set Disabled = true on an individual MSelectOption to prevent selecting that specific item.
<div class="relative max-w-xs">
<MSelect Items="_fruits"
Placeholder="Choose a fruit"
Class="w-full rounded-md border border-slate-300 bg-white pl-3 pr-10 py-2 text-sm text-slate-900 appearance-none focus:outline-none focus:ring-2 focus:ring-sky-500" />
<div class="pointer-events-none absolute top-1/2 -translate-y-1/2 right-3">
<svg class="h-4 w-4 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</div>
</div>
@code {
private static readonly MSelectOption[] _fruits =
[
new("apple", "Apple"),
new("banana", "Banana"),
new("cherry", "Cherry (unavailable)", Disabled: true),
new("mango", "Mango"),
];
}
In a Form
Use @bind-Value inside an EditForm for model binding and validation support.
Supply a ValueExpression when you need <ValidationMessage> to identify the field.
With @bind-Value this is handled automatically by the Blazor compiler.
<EditForm Model="_model" OnValidSubmit="HandleSubmit">
<DataAnnotationsValidator />
<div class="flex flex-col gap-2 max-w-xs">
<MLabel For="role">Role</MLabel>
<div class="relative">
<MSelect id="role"
Items="_roles"
@bind-Value="_model.Role"
Placeholder="Select a role"
Class="w-full rounded-md border border-slate-300 bg-white pl-3 pr-10 py-2 text-sm text-slate-900 appearance-none focus:outline-none focus:ring-2 focus:ring-sky-500 disabled:cursor-not-allowed disabled:opacity-50" />
<div class="pointer-events-none absolute top-1/2 -translate-y-1/2 right-3">
<svg class="h-4 w-4 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</div>
</div>
<ValidationMessage For="() => _model.Role" />
</div>
<MButton Type="submit" Class="mt-4">Submit</MButton>
</EditForm>
@code {
private readonly MyModel _model = new();
private static readonly MSelectOption[] _roles =
[
new("admin", "Admin"),
new("editor", "Editor"),
new("viewer", "Viewer"),
];
private void HandleSubmit() { /* ... */ }
}
Standard HTML Attributes
MSelect supports all standard HTML select attributes via AdditionalAttributes.
Pass any attribute directly — id, name, aria-label, aria-describedby — and it will be forwarded to the underlying <select>.
Your role determines your access level.
<div class="flex flex-col gap-2 max-w-xs">
<MLabel For="role-select">Role</MLabel>
<div class="relative">
<MSelect id="role-select"
name="role"
aria-label="Select your role"
aria-describedby="role-hint"
Items="_roles"
Placeholder="Choose a role"
Class="w-full rounded-md border border-slate-300 bg-white pl-3 pr-10 py-2 text-sm text-slate-900 appearance-none focus:outline-none focus:ring-2 focus:ring-sky-500" />
<div class="pointer-events-none absolute top-1/2 -translate-y-1/2 right-3">
<svg class="h-4 w-4 text-slate-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</div>
</div>
<p id="role-hint" class="text-sm text-muted-foreground">Your role determines your access level.</p>
</div>
@code {
private static readonly MSelectOption[] _roles =
[
new("admin", "Admin"),
new("editor", "Editor"),
new("viewer", "Viewer"),
];
}
- Width:
Class="w-48"orClass="max-w-xs" - Font:
Class="font-medium"orClass="text-base" - Accessibility:
aria-label="...",aria-describedby="..." - Forms:
name="..."for form submission grouping - Testing:
data-testid="...",id="..."
Accessibility
- Renders a native
<select>— fully keyboard accessible out of the box - Always associate a visible label using
MLabelwith matchingidandForattributes - Use
aria-labelwhen a visible label is not present - Use
aria-describedbyto link help text or error messages to the select - Disabled options use the
disabledattribute on the<option>element - Disabled select uses the native
disabledattribute; apply visual disabled styling (cursor, opacity) via yourClassstring
API Reference
MSelect
| Parameter | Type | Default | Description |
|---|---|---|---|
| Value | string? | null |
The currently selected value. Use with @bind-Value for two-way binding. |
| ValueChanged | EventCallback<string?> | — |
Callback invoked when the selection changes. Wired automatically by @bind-Value. |
| ValueExpression | Expression<Func<string?>>? | null |
Expression identifying the bound field for <ValidationMessage>. Set automatically by @bind-Value. |
| Placeholder | string? | null |
When set, renders a leading disabled empty option with this text as a prompt. |
| Items | IEnumerable<MSelectOption>? | null |
The collection of options to render in the select. |
| Size | Size | Size.Default |
Height, padding, and font-size preset (Sm, Default, Lg). Matches MInput sizing. |
| Disabled | bool | false |
Sets the native disabled attribute on the <select>. Default styling applies disabled:cursor-not-allowed disabled:opacity-50. |
| Class | string? | null |
Additional Tailwind classes applied to the native <select>. User-supplied classes win all conflicts. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes forwarded to the <select> element (e.g. id, name, aria-*, data-*). |
MSelectOption
| Property | Type | Default | Description |
|---|---|---|---|
| Value | string | — |
The value submitted when this option is selected. |
| Label | string | — |
The display text shown in the option list. |
| Disabled | bool | false |
When true, the option is rendered but cannot be selected. |
- Always pair with
MLabelusing matchingidandForfor accessible association - Use
Placeholderrather than a blank default option to guide users - Use
@bind-Valuefor reactive state over manualValue+ValueChanged - Mark unavailable options with
Disabled: truerather than removing them — this preserves context - Add
nameattribute when the select is inside a native HTML form - Add
data-testidattributes to target selects in automated tests - Add
appearance-noneto yourClassstring if you want to suppress the browser-native arrow and supply your own