SegmentedControl
An interactive page: C# runs over a Blazor Server connection.
Usage
@* An enum: each value is a segment, named by its [Display(Name)]. *@
<SegmentedControl @bind-Value="Model!.View" />
@* Icons only. *@
<SegmentedControl @bind-Value="Model!.Align" ItemIcon="AlignIcon" IconOnly />
@* Other values, with ItemText for each one's text. *@
<SegmentedControl @bind-Value="Model!.Range" Items="Ranges.Keys" ItemText="key => Ranges[key]" />It's a RadioGroup drawn as joined buttons: it posts the same way and works with JavaScript off, and a post can carry a value that isn't among the options, so check it on the server. Use it for two to five short options; for more, or longer ones, use a radio group or a Select, and to switch between panels of content, Tabs.
Icons
Use ItemIcon to put an icon before each option's text, and IconOnly to show only the icons.
Preview
Code
System.ComponentModel.DataAnnotations
<Form Model="Model" FormName="segmented-control-icons" Grow>
<SegmentedControl @bind-Value="Model!.Theme" ItemIcon="ThemeIcon" />
<SegmentedControl @bind-Value="Model!.Align" ItemIcon="AlignIcon" IconOnly />
</Form>
{
[SupplyParameterFromForm(FormName = "segmented-control-icons")]
private Editor? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private static IconData? ThemeIcon(Theme theme) => theme switch
{
Theme.Light => Lucide.Sun,
Theme.Dark => Lucide.Moon,
_ => Lucide.Monitor,
};
private static IconData? AlignIcon(TextAlignment align) => align switch
{
TextAlignment.Left => Lucide.TextAlignStart,
TextAlignment.Center => Lucide.TextAlignCenter,
TextAlignment.Right => Lucide.TextAlignEnd,
_ => Lucide.TextAlignJustify,
};
public sealed class Editor
{
public Theme Theme { get; set; } = Theme.System;
[Display(Name = "Text alignment")]
public TextAlignment Align { get; set; } = TextAlignment.Left;
}
public enum Theme { Light, Dark, System }
public enum TextAlignment { Left, Center, Right, Justify }
}Full width, no visible label
Use FullWidth to share the width equally between the segments, and HideLabel when something near it, such as a heading, already says what it's for.
Preview
Code
System.ComponentModel.DataAnnotations
<Form Model="Model" FormName="segmented-control-full-width" MaxWidth="24.Rem" Grow>
<Heading Level="3" Size="TextSize.Lg">Pay for Pro monthly or yearly</Heading>
<SegmentedControl @bind-Value="Model!.Billing" FullWidth HideLabel />
<FormActions><Button Submit>Continue to payment</Button></FormActions>
</Form>
{
[SupplyParameterFromForm(FormName = "segmented-control-full-width")]
private Plan? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
public sealed class Plan
{
public Billing Billing { get; set; } = Billing.Yearly;
}
public enum Billing
{
Monthly,
[Display(Name = "Yearly (−20%)")] Yearly,
}
}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 control is disabled while a hidden field posts its value.
Preview
Code
<Form Model="Model" FormName="segmented-control-sizes" Grow>
<Row Gap="4" Wrap Align="Align.End">
<SegmentedControl @bind-Value="Model!.Small" Label="Small" Size="ControlSize.Sm" />
<SegmentedControl @bind-Value="Model!.Medium" Label="Medium" />
<SegmentedControl @bind-Value="Model!.Large" Label="Large" Size="ControlSize.Lg" />
</Row>
<Row Gap="4" Wrap>
<SegmentedControl @bind-Value="Model!.Locked" Label="Disabled" Disabled />
</Row>
</Form>
{
[SupplyParameterFromForm(FormName = "segmented-control-sizes")]
private Views? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
public sealed class Views
{
public Period Small { get; set; } = Period.Week;
public Period Medium { get; set; } = Period.Week;
public Period Large { get; set; } = Period.Week;
public Period Locked { get; set; } = Period.Day;
}
public enum Period { Day, Week, Month }
}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
| Feature | Off | Static | Live |
|---|---|---|---|
| Everything | Yes | Yes | Yes |
Off: JavaScript disabled. Static: a static SSR page with IzzyBlazor's script. Live: an interactive page (Server, WebAssembly or Auto).
Accessibility
- Screen readers hear a radio group: a
fieldsetnamed by itslegend, with a native radio in each segment, so Tab reaches the group and the arrow keys move the choice. - The focus ring is drawn around the focused segment.
- After a post that fails with an error here, the checked segment (or the first) takes focus, so screen readers read the group's name and error (its description); a
FormErrorsearlier in the page takes it first. Not on interactive pages: there, use aFormErrors. - With
IconOnly, each option's text stays for screen readers and shows as its hover tooltip. - A field with no visible label needs
HideLabel, not an empty label, so the group keeps its name.
Parameters
| Prop | Type | Default |
|---|---|---|
ItemsThe options. | IEnumerable<TValue>? | |
ItemTextThe text for each option. | Func<TValue, string>? | computed |
ItemIconAn icon for each option, before its text. | Func<TValue, IconData?>? | |
IconOnlyShows only the icons; the text stays for screen readers and as a tooltip. | bool | false |
FullWidthFills the available width, the segments sharing it equally. | bool | false |
@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? |
Plus 26 field props.
Full APISee also
- RadioGroup: one choice from a list of radios.
- Tabs: panels that switch with no script, or links to pages.
- Switch: an on/off switch that posts like a checkbox.
- ButtonGroup: joined or spaced buttons with shared defaults.