RadioGroupRef
Single-choice picker rendered as a group of radio inputs over a fixed list of options. Tab-owned: the browser holds the canonical selected value.
kind
input
defaults (class-level)
| field | type | default | notes |
|---|---|---|---|
options | list[dict[str, str]] | [] | each entry is {"value": str, "label": str}. bare-string entries are accepted and normalized to dict form. |
selected | str | "" | initial selected value. should match an option value; empty means nothing selected. |
orientation | str | "vertical" | layout direction for the radio items. one of "vertical", "horizontal". |
Class attributes on the python RadioGroupRef. Shipped on mount under the field entry's props key after mount_props normalizes options into the dict form. Later server -> tab writes override slice values.
interactions
| op | dir | payload | nu method | notes |
|---|---|---|---|---|
write | server -> tab | str (selected value) or {"options": list[{value, label}]} | store, store_options | string payload replaces the selected value; map with options swaps the list. |
read | server -> tab | request null; resp str | (via aeval) | request/response, correlated by id. resolves the live selected value. |
notify | tab -> server | null | changed | emitted on every user pick. |
Nu methods:
RadioGroupRef.aevalships areadviasession.aread, returns the selected value asstr.RadioGroupRef.store(value)compiles towritewith a bare string payload.RadioGroupRef.store_options(opts)compiles towritewith{"options": [...]}. Accepts strings or dicts; normalized server-side.RadioGroupRef.changed()returns aChangedsubscription driven by inboundnotifyframes.
wire payloads
// mount field entry (inside the mount payload)
{"path": "HomePage.size", "type": "RadioGroupRef", "props": {
"options": [{"value": "s", "label": "Small"}, {"value": "m", "label": "Medium"}, {"value": "l", "label": "Large"}],
"selected": "m",
"orientation": "vertical"
}}
// server pushes a selected value
{"op": "write", "ref": "HomePage.size", "payload": "l"}
// server swaps the option list
{"op": "write", "ref": "HomePage.size", "payload": {"options": [{"value": "xs", "label": "XS"}, {"value": "xl", "label": "XL"}]}}
// server fetches
{"op": "read", "ref": "HomePage.size", "payload": null, "id": "ab12"}
{"op": "read", "ref": "HomePage.size", "payload": "l", "id": "ab12"}
// browser tells server the user picked something
{"op": "notify", "ref": "HomePage.size", "payload": null}write payload is a msgpack string (selected push) or a msgpack map with an options key (list swap). read-response is a msgpack string; notify and read-request are msgpack nil.
renderer
web/src/refs/radio-group.tsx. Native <input type="radio"> per option, all sharing a name derived from the wire path. Items wrap in a flex container -- flex-col gap-2 when orientation === "vertical", flex-row gap-4 when "horizontal". Tailwind utility classes match select.tsx; no shadcn dep. Each item is a <label> wrapping the radio and its label text.
slice shape
{
type: "RadioGroupRef",
value: null,
selected: string,
options: Array<{ value: string; label: string }>,
orientation: "vertical" | "horizontal",
write: (v: unknown) => void,
get: () => string,
}Seeded from field.props on mount. write dispatches on payload shape: a map with options overwrites the option list; anything else is coerced via String(v) and assigned to selected (nil becomes ""). get reads from the live store to avoid stale closures.
edge cases
writepayload is msgpack nil: sliceselectedbecomes""; no radio is checked.writepayload is a string not present inoptions: still stored on the slice; no radio renders as checked until the next pick.writepayload is{"options": [...]}and the currentselectedis not in the new list:selectedis left untouched; no radio renders as checked.writepayload is a non-string, non-map (e.g. number): coerced viaString(v).optionsentry missingvalueorlabel: coerced viaString(...); nil becomes"".- slot never written and no
propson mount: slice defaults toselected="",options=[],orientation="vertical". orientationprop unrecognized: slice falls back to"vertical".- empty
options: nothing is rendered inside the container. - no debounce: every user pick ships one
notify.
non-goals
- no multi-select. that is a separate
CheckboxGroupRef. - no per-option disabled state.
- no grouped / nested options.
- no inline description / helper text per option (label only).
- no keyboard roving-tabindex custom behavior; rely on native radio semantics.
- no form integration beyond the standard slice contract.