Skip to main content
On this pageFunctions

Ui/RadioGroup

Functions

view

functionsource
/**
 * Renders an accessible radio group as a stateless controlled component. The
 *  parent owns the selection (`selectedValue`) and receives the parent's own
 *  Message via `onSelect` when an option is chosen. Moving focus onto the
 *  newly-active option is the radio group's own concern: it happens inside the
 *  component's click and keydown handlers, so the parent's `update` only stores
 *  the value.
 * 
 *  ```ts
 *  // In view:
 *  RadioGroup.view<Tool, Message>({
 *    id: TOOL_RADIO_GROUP_ID,
 *    selectedValue: Option.some(model.tool),
 *    options: TOOLS,
 *    ariaLabel: 'Tool',
 *    onSelect: tool => SelectedTool({ tool }),
 *    toView: ({ group, options }) => ...,
 *  })
 * 
 *  // In update:
 *  SelectedTool: ({ tool }) => [evo(model, { tool: () => tool }), []],
 *  ```
 */
<Value extends string, ParentMessage>(config: ViewConfig<Value, ParentMessage>): Html

Types

OptionInfo

typesource
/**
 * Per-option render info passed to the consumer's `toView`. The consumer
 *  spreads `option`, `label`, and `description` onto whichever elements carry
 *  that role in their layout. Generic over `Value extends string` so
 *  `option.value` carries the consumer's union type, and over `ParentMessage`
 *  so the attribute bundles dispatch the parent's own Message.
 */
type OptionInfo = Readonly<{
  description: ReadonlyArray<Attribute<ParentMessage>>
  index: number
  isActive: boolean
  isDisabled: boolean
  isSelected: boolean
  label: ReadonlyArray<Attribute<ParentMessage>>
  option: ReadonlyArray<Attribute<ParentMessage>>
  value: Value
}>

RenderInfo

typesource
/**
 * Render-time payload published to the consumer's `toView`.
 * 
 *  - `group`: ARIA + role attributes for the wrapping radiogroup element.
 *  - `options`: one entry per option in `options`, in the same order. Includes
 *    the value, derived state, and the attribute bundles for the option
 *    element, its label, and its description.
 *  - `selectedValue`: the currently-selected value, if any. Convenient for the
 *    consumer when rendering selected-state visuals next to the option
 *    attributes.
 *  - `hiddenInput`: when `name` was supplied, attributes for a hidden form
 *    input carrying the selected value. The consumer renders the `<input>`
 *    themselves. Empty array when `name` is undefined.
 */
type RenderInfo = Readonly<{
  group: ReadonlyArray<Attribute<ParentMessage>>
  hiddenInput: ReadonlyArray<Attribute<ParentMessage>>
  options: ReadonlyArray<OptionInfo<Value, ParentMessage>>
  selectedValue: Option.Option<Value>
}>

ViewConfig

typesource
/**
 * Per-render view configuration for the stateless controlled view.
 *  Generic over `Value extends string` (the option union) and `ParentMessage`
 *  (the message `onSelect` dispatches).
 * 
 *  - `selectedValue`: the current selection, read straight from the parent
 *    Model. The roving tabindex and checked state derive from it.
 *  - `onSelect`: dispatched with the chosen value when an option is clicked or
 *    navigated to. Handle it in the parent's `update` by storing the value.
 *    The radio group manages focus itself, so the handler needs to do nothing
 *    else.
 *  - `toView`: receives the RenderInfo and lays out the group.
 */
type ViewConfig = Readonly<{
  ariaLabel: string
  id: string
  isDisabled: boolean
  isOptionDisabled: (value: Value, index: number) => boolean
  name: string
  onSelect: (value: Value) => ParentMessage
  options: ReadonlyArray<Value>
  orientation: Orientation
  selectedValue: Option.Option<Value>
  toView: (render: RenderInfo<Value, ParentMessage>) => Html
}>

Constants

Orientation

constsource
/** Controls the radio group layout direction and which arrow keys navigate between options. */
const Orientation: Literals<readonly ["Horizontal", "Vertical"]>

Stay in the update loop.

New releases, patterns, and the occasional deep dive.


Built with Foldkit.

© 2026 Devin Jameson