M
Marai.UI
v1.0.0-alpha.7

Radio Group

Mutually exclusive selection within a group. Compose MRadioGroup with one or more MRadioButton items for accessible single-choice inputs.

Overview

The MRadioGroup and MRadioButton components work together to provide accessible, single-choice selection. MRadioGroup manages shared state through a cascading context, automatically wiring the name attribute and selection tracking across all child MRadioButton items. Supports Blazor two-way binding via @bind-Value and integrates with EditForm for model validation.

Usage

Add the namespace import and include the stylesheet reference if not already done.

razor
@using Marai.UI.Components.MRadioButton

Basic Usage

Preview
razor
<MRadioGroup>
    <MRadioButton Value="option-a" Label="Option A" />
    <MRadioButton Value="option-b" Label="Option B" />
    <MRadioButton Value="option-c" Label="Option C" />
</MRadioGroup>

Examples

Default Selected Value

Pass a Value to pre-select an option on render.

Preview
razor
<MRadioGroup Value="comfortable">
    <MRadioButton Value="default" Label="Default" />
    <MRadioButton Value="comfortable" Label="Comfortable" />
    <MRadioButton Value="compact" Label="Compact" />
</MRadioGroup>

Two-Way Binding

Use @bind-Value to keep local state in sync with the selection.

Preview

Selected: system

razor
<div style="display:flex;flex-direction:column;gap:0.75rem;">
    <MRadioGroup @bind-Value="_theme">
        <MRadioButton Value="light" Label="Light" />
        <MRadioButton Value="dark" Label="Dark" />
        <MRadioButton Value="system" Label="System" />
    </MRadioGroup>
    <p class="text-sm text-muted-foreground">Selected: @_theme</p>
</div>

@code {
    private string _theme = "system";
}

Disabled Group

Set Disabled="true" on MRadioGroup to disable all items at once.

Preview
razor
<MRadioGroup Value="option-b" Disabled="true">
    <MRadioButton Value="option-a" Label="Option A" />
    <MRadioButton Value="option-b" Label="Option B" />
    <MRadioButton Value="option-c" Label="Option C" />
</MRadioGroup>

Disabled Item

Set Disabled="true" on an individual MRadioButton to disable only that option.

Preview
razor
<MRadioGroup Value="option-a">
    <MRadioButton Value="option-a" Label="Option A" />
    <MRadioButton Value="option-b" Label="Option B (unavailable)" Disabled="true" />
    <MRadioButton Value="option-c" Label="Option C" />
</MRadioGroup>

In a Form

Use @bind-Value inside an EditForm for model binding and validation.

razor
<EditForm Model="_model" OnValidSubmit="HandleSubmit">
    <DataAnnotationsValidator />
    <MRadioGroup @bind-Value="_model.Plan">
        <MRadioButton Value="free" Label="Free" />
        <MRadioButton Value="pro" Label="Pro" />
        <MRadioButton Value="enterprise" Label="Enterprise" />
    </MRadioGroup>
    <ValidationMessage For="() => _model.Plan" />
    <MButton Type="submit" Class="mt-4">Continue</MButton>
</EditForm>

@code {
    private readonly PlanModel _model = new();

    private void HandleSubmit() { /* ... */ }
}

Customization

Custom Tailwind CSS Classes

Use Class on MRadioGroup to control layout and spacing between items, or on individual MRadioButton items for per-item overrides.

Preview
razor
<MRadioGroup Class="gap-4">
    <MRadioButton Value="a" Label="Option A" Class="font-semibold" />
    <MRadioButton Value="b" Label="Option B" Class="font-semibold" />
    <MRadioButton Value="c" Label="Option C" Class="font-semibold" />
</MRadioGroup>

Standard HTML Attributes

Both MRadioGroup and MRadioButton forward arbitrary HTML attributes to their root elements. Use aria-label on the group for screen readers, and id on individual buttons for precise targeting.

Preview

Choose how often you receive updates.

razor
<MRadioGroup aria-label="Notification preferences" aria-describedby="notif-hint">
    <MRadioButton Value="all" Label="All notifications" id="notif-all" />
    <MRadioButton Value="important" Label="Important only" id="notif-important" />
    <MRadioButton Value="none" Label="None" id="notif-none" />
</MRadioGroup>
<p id="notif-hint" class="text-sm text-muted-foreground">Choose how often you receive updates.</p>
Customization Examples
  • Group spacing: Class="gap-4" on MRadioGroup for more space between items
  • Horizontal layout: Class="flex-row gap-6" on MRadioGroup
  • Item styling: Class="font-semibold" on MRadioButton
  • Accessibility: aria-label="..." on the group, aria-describedby="..." on items
  • Testing: id="..." or data-testid="..." on individual buttons

To retheme the radio accent color, override CSS variables in your stylesheet:

css
:root {
    --primary: 222 84% 30%;
    --ring:    222 84% 30%;
}

Accessibility

  • MRadioGroup renders a <div role="radiogroup"> — use aria-label or aria-labelledby to identify the group for screen readers
  • The name attribute is auto-generated per group instance, so radio buttons are correctly grouped without manual wiring
  • Each MRadioButton renders a <label> wrapping the input — clicking the label text activates the radio
  • Disabled items use the disabled attribute and cursor-not-allowed styling
  • Focus ring is applied via focus-visible:ring-2 for keyboard navigation visibility
  • Arrow keys navigate between options natively — no JavaScript required

API Reference

MRadioGroup

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 use with <ValidationMessage>.
Disabled bool false Disables all child radio buttons in the group.
Class string? null Additional CSS class names appended to the group wrapper element.
ChildContent RenderFragment? null The MRadioButton items to render inside the group.
AdditionalAttributes Dictionary<string, object>? null Arbitrary HTML attributes passed through to the group <div> (e.g. aria-label, id).

MRadioButton

Parameter Type Default Description
Value string "" The value this radio button represents. Must be unique within the group.
Label string? null Text label rendered next to the radio input. Use ChildContent for richer markup.
ChildContent RenderFragment? null Optional rich label content rendered after the radio input. Ignored if Label is set.
Disabled bool false Disables this individual radio button. Also disabled when the parent group is disabled.
Class string? null Additional CSS class names appended to the item wrapper <label> element.
AdditionalAttributes Dictionary<string, object>? null Arbitrary HTML attributes passed through to the <input> element (e.g. aria-describedby, id).

Styling Details

Default Styling

The group renders a vertical flex column and each item uses a label wrapper for click area coverage:

  • Group: flex flex-col gap-3 — vertical stack with 12px between items
  • Item wrapper: flex items-center gap-3 — horizontally aligns input and label with 12px gap
  • Input: h-4 w-4 rounded-full — 16px circle with native checked styling via accent-primary
  • Label text: text-sm font-medium leading-none — compact, readable label
  • Focus: focus-visible:ring-2 focus-visible:ring-ring — keyboard focus ring
  • Disabled: disabled:cursor-not-allowed disabled:opacity-50 on input, peer-disabled:opacity-70 on label
Best Practices
  • Always provide a Label or ChildContent — radio buttons without labels are inaccessible
  • Add aria-label to MRadioGroup when there is no visible heading for the group
  • Use @bind-Value instead of manually managing selection state
  • Disable individual items via Disabled on MRadioButton rather than hiding them
  • Keep option labels concise — for longer descriptions use ChildContent with aria-describedby
  • Prefer vertical layout (default) for three or more options to reduce scanning effort