# RadioGroup A group of radio buttons sharing the same `name` attribute. Supports horizontal or vertical layout. One option can be pre-selected. --- ## HTML structure ``` div.flex.flex-col.gap-1.5 label.text-sm.font-medium ← group label (omitted when empty) {label} div.flex.{direction}.gap-3 ← flex-col or flex-row label.flex.items-center.gap-2.cursor-pointer ← one per option input[type=radio, name, value, class, $$Checked$$] span.text-sm {option label} ``` --- ## CSS mechanics | Class | Effect | |---|---| | `accent-primary` | Radio circle color follows `--color-primary` CSS variable | | `h-4 w-4` | 16×16 radio circle | | `cursor-pointer` | Pointer cursor on the label | | `flex-col` (default) | Stacks options vertically | | `flex-row` | Places options side by side | --- ## Constructor signature ```csharp public RadioGroup( string name, IEnumerable<(string Value, string Label, bool Selected)> options, string label = "", string direction = "flex-col") ``` | Parameter | Description | |---|---| | `name` | Shared `name` attribute for all radio inputs in the group | | `options` | List of `(Value, Label, Selected)` tuples | | `label` | Optional visible group heading above the options | | `direction` | `"flex-col"` (vertical, default) or `"flex-row"` (horizontal) | --- ## Usage examples ### Vertical list ```csharp new RadioGroup( name: "plan", label: "Select a plan", options: new[] { ("free", "Free", true), ("pro", "Pro", false), ("teams", "Teams", false), }) ``` ### Horizontal inline options ```csharp new RadioGroup( name: "size", label: "Size", direction: "flex-row", options: new[] { ("sm", "S", false), ("md", "M", true), ("lg", "L", false), ("xl", "XL", false), }) ``` ### Reading in a form handler ```csharp public record Command([property: FromForm] string Plan); // command.Plan == "free" | "pro" | "teams" ``` ### Dynamic options from database ```csharp var options = categories .Select((cat, i) => (cat.Slug, cat.Name, i == 0)) .ToArray(); new RadioGroup(name: "category", label: "Category", options: options) ``` --- ## 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

Quick survey

Help us improve BeepBoop.

$$AntiforgeryToken$$
$$ExperienceGroup$$ $$FeatureGroup$$
$$SubmitBtn$$
$$SuccessAlert$$
``` **`Templates/SurveyPage.htmx.cs`** ```csharp 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 = $"""""".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** ```csharp [Handler] [MapPost("/survey")] public static partial class PostSurveyHandler { public record Command( [property: FromForm] string Experience, [property: FromForm] string Favourite); private static Task 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`** ```csharp [JsonSerializable(typeof(PostSurveyHandler.Command), TypeInfoPropertyName = "SurveyCommand")] ```