Files
Htmx/docs/Components/RadioGroup.md
T
2026-05-05 23:55:26 +05:00

6.5 KiB

RadioGroup

A set of radio buttons where only one option can be selected at a time. Use it when you want the user to pick exactly one value from a short list — pricing plans, delivery options, account types.


Quick example

new RadioGroup(
    name:  "plan",
    label: "Select a plan",
    options: new[]
    {
        ("free",  "Free",  true),   // pre-selected
        ("pro",   "Pro",   false),
        ("teams", "Teams", false),
    })

All the options

public RadioGroup(
    string name,
    IEnumerable<(string Value, string Label, bool Selected)> options,
    string label     = "",
    string direction = "flex-col")
Parameter What it does
name The shared form field name for all radio buttons in the group.
options The list of choices. Each is a (Value, Label, Selected) tuple.
label Optional heading displayed above the options.
direction "flex-col" stacks options vertically (default). "flex-row" places them side by side.

Option tuple fields:

Field What it does
Value The string submitted when this option is selected.
Label The text shown next to the radio button.
Selected Pre-select this option on render. Only one should be true.

Real-world examples

Pricing plan selector

new RadioGroup(
    name:    "plan",
    label:   "Choose your plan",
    options: new[]
    {
        ("free",       "Free — up to 3 projects",     true),
        ("pro",        "Pro — unlimited projects",    false),
        ("enterprise", "Enterprise — custom pricing", false),
    })

Reading on the server:

public record Command([property: FromForm] string Plan);
// command.Plan == "free" | "pro" | "enterprise"

Horizontal size selector

new RadioGroup(
    name:      "size",
    label:     "Size",
    direction: "flex-row",
    options: new[]
    {
        ("sm", "S",  false),
        ("md", "M",  true),
        ("lg", "L",  false),
        ("xl", "XL", false),
    })

Options built dynamically from the database

var options = categories
    .Select((cat, i) => (cat.Slug, cat.Name, i == 0))  // first option pre-selected
    .ToArray();

new RadioGroup(name: "category", label: "Category", options: options)

How it works

Each option is a <label> element containing a native <input type="radio"> and a <span> with the label text. Because the <input> is inside the <label>, clicking anywhere on the label text selects the option. All radio buttons in the group share the same name attribute — the browser ensures only one can be selected at a time.

The radio dot colour follows your primary theme colour via accent-primary.


---

## Tips and tricks

- Only one option in the group can have `Selected = true`; if multiple are marked selected the last one wins (standard HTML behavior).
- An unselected `RadioGroup` submits nothing — validate server-side that the field is present.
- For a "none of the above" option, add a tuple with the intended empty value: `("", "None", false)`.
- To conditionally show additional fields when a radio is selected, add an `htmx` attribute via inline HTML after the component — or use a custom slot that includes both the radio and a reveal div.
- To conditionally show additional fields when a radio is selected, add an `htmx` attribute via inline HTML after the component — or use a custom slot that includes both the radio and a reveal div.

---

## Complete page example

**`Templates/SurveyPage.htmx`**
```html
<div class="max-w-md mx-auto py-10">
  <h1 class="text-2xl font-bold mb-2">Quick survey</h1>
  <p class="text-sm text-muted-foreground mb-8">Help us improve BeepBoop.</p>
  <form method="post" action="/survey">
  $$AntiforgeryToken$$
  <div class="space-y-8 mb-8">
    $$ExperienceGroup$$
    $$FeatureGroup$$
  </div>
  $$SubmitBtn$$
  </form>
  $$SuccessAlert$$
</div>

Templates/SurveyPage.htmx.cs

namespace Htmx.ApiDemo.Templates;

public sealed class SurveyPage : SurveyPageBase
{
  private readonly IHtmxComponent _experience;
  private readonly IHtmxComponent _feature;
  private readonly IHtmxComponent _submit;
  private readonly IHtmxComponent _success;
  private readonly byte[] _afToken;

  public SurveyPage(IAntiforgery af, HttpContext ctx, bool submitted = false)
  {
    var tokens = af.GetAndStoreTokens(ctx);
    _afToken = $"""<input type="hidden" name="{tokens.FormFieldName}" value="{tokens.RequestToken}">""".ToUtf8Bytes();

    _experience = new Components.RadioGroup(
      name:  "experience",
      label: "How would you rate your experience?",
      options: new[]
      {
        ("1", "Poor",      false),
        ("2", "Fair",      false),
        ("3", "Good",      true),
        ("4", "Very good", false),
        ("5", "Excellent", false),
      },
      direction: "flex-row");

    _feature = new Components.RadioGroup(
      name:  "favourite",
      label: "Which feature do you use most?",
      options: new[]
      {
        ("htmx",      "HTMX integration", false),
        ("aot",       "AOT publishing",    false),
        ("generator", "Source generator",  false),
        ("tailwind",  "Tailwind CSS",       false),
      });

    _submit  = new Components.Button("Submit", type: "submit");
    _success = submitted
      ? new Components.Alert(title: "Thank you!", description: "Your responses have been recorded.")
      : HtmxEmpty.Instance;
  }

  protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afToken);
  protected override void RenderExperienceGroup(HtmxRenderContext ctx)  => _experience.Render(ctx.Next());
  protected override void RenderFeatureGroup(HtmxRenderContext ctx)     => _feature.Render(ctx.Next());
  protected override void RenderSubmitBtn(HtmxRenderContext ctx)        => _submit.Render(ctx.Next());
  protected override void RenderSuccessAlert(HtmxRenderContext ctx)     => _success.Render(ctx.Next());
}

POST handler

[Handler]
[MapPost("/survey")]
public static partial class PostSurveyHandler
{
  public record Command(
    [property: FromForm] string Experience,
    [property: FromForm] string Favourite);

  private static Task<IResult> HandleAsync(
    [AsParameters] Command cmd, HttpContext ctx, IAntiforgery af, CancellationToken ct)
  {
    // Persist responses…
    return ctx.WriteHtmxPage(new SurveyPage(af, ctx, submitted: true), title: "Survey");
  }
}

AppJsonSerializerContext.cs

[JsonSerializable(typeof(PostSurveyHandler.Command), TypeInfoPropertyName = "SurveyCommand")]