Skip to content
IzzyBlazor 0.1.0-alpha.17

Enter all results ↑ ↓ move Esc close

SegmentedControl

One choice from a few, as joined buttons.

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

Usage

razor
@* 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

Theme
Text alignment

Code

Icons.razor
@using 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>

@code {
    [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

Pay for Pro monthly or yearly

Billing

Code

FullWidth.razor
@using 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>

@code {
    [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

Small
Medium
Large
Disabled

Code

Sizes.razor
<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>

@code {
    [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

What works in each mode, SegmentedControl
FeatureOffStaticLive
EverythingYesYesYes

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 fieldset named by its legend, 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 FormErrors earlier in the page takes it first. Not on interactive pages: there, use a FormErrors.
  • 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

SegmentedControl parameters
PropTypeDefault
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.boolfalse
FullWidthFills the available width, the segments sharing it equally.boolfalse
@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

  • 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.