Radio group
In reviewLive · web
Vertical list: label + description, one chosen (the filled dot). Tab in, Arrow to move + select
Session engine
Inline / horizontal: compact single choice on one line
Visibility
Radio cards: selectable cards, the chosen one gets a cyan ring + wash
Default model
Disabled option: inert but still legible (Plan mode is locked)
Tool approvals
Error / required: warm ring + a message (color is never alone)
Delivery methodSelect an option to continue
Sizes: sm and md
Engine (small)
Region (medium)
Selection changed 0 times. The island is live.
// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform;// this is the canonical API the published @dizyx/nockerl-react package exposes.)import { NockerlRadioGroup } from '@dizyx/nockerl-react';
export function EngineChoice() { const [engine, setEngine] = useState('api-server'); return ( <NockerlRadioGroup label="Session engine" value={engine} onChange={setEngine} options={[ { value: 'cloud-agent', label: 'Cloud Agent', description: 'SDK manages context in JSONL' }, { value: 'api-server', label: 'Local Engine', description: 'Our lossless context management' }, ]} /> );}
// Radio cards: same control, variant="card" + orientation="horizontal".<NockerlRadioGroup label="Default model" variant="card" orientation="horizontal" value={model} onChange={setModel} options={MODELS} />// Android: Jetpack Compose (canonical). Material3 RadioButton, brand-themed.// The M3 radio-row idiom: the PARENT Row owns the click via Modifier.selectable// with Role.RadioButton, so the whole label is one a11y unit; the child// RadioButton takes onClick = null. (chat/ui/SessionEngineSupport.kt)import androidx.compose.material3.RadioButtonimport androidx.compose.material3.RadioButtonDefaultsimport androidx.compose.foundation.selection.selectableGroupimport androidx.compose.foundation.selection.selectableimport androidx.compose.ui.semantics.Role
Column(Modifier.selectableGroup()) { engines.forEach { engine -> val selected = engine == selectedEngine Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .selectable( selected = selected, onClick = { onEngineChange(engine) }, role = Role.RadioButton, ), ) { RadioButton( selected = selected, onClick = null, colors = RadioButtonDefaults.colors( selectedColor = LocalNockerlColors.current.accentPrimary, ), ) Text(engine.label, style = MaterialTheme.typography.bodyMedium) } }}// macOS: SwiftUI (canonical). There is NO radio control on macOS. Single// choice ships as the custom SegmentedSelector (the active option is a cyan// OUTLINE, not a fill). NockerlVoice/UI/SettingsComponents.swift +// AppSettingsView.swift. The platform Picker(.inline / .segmented) is the// system stand-in. See the drift note below.SegmentedSelector( options: SessionEngine.allCases, selection: $engine) { $0.label }
// Native alternative for long lists, an inline Picker (chosen row = checkmark):Picker("Session engine", selection: $engine) { ForEach(SessionEngine.allCases) { engine in Text(engine.label).tag(engine) }}.pickerStyle(.inline)Parameters
Section titled “Parameters”| Prop | Type | Default | Description |
|---|---|---|---|
options * | RadioOption[] | Group options, rendered in order. | |
value * | string | Currently selected value (controlled). | |
onChange | (next: string) => void | Fired with the next value. Ignored for disabled options. | |
label * | string | Visible group label, wired as the radiogroup's accessible name. | |
orientation | 'vertical' | 'horizontal' | 'vertical' | Stack vertically (default) or flow inline on one line. |
size | RadioSize | 'md' | Circle scale. md matches the platform default. |
invalid | boolean | false | Whole-group invalid: warm error ring + a required message. Color never alone. |
variant | 'list' | 'card' | 'list' | Render each option as a selectable CARD instead of a list row. |
Also accepts the native <div> attributes (e.g. onClick, disabled, id, aria-*, data-*), forwarded straight through, plus a forwarded ref.
| Parameter | Type | Default | Description |
|---|---|---|---|
selected * | Boolean | Whether this option is the chosen one. Drives the centered dot. | |
onClick | (() -> Unit)? | null | Pass null on the RadioButton. The parent Modifier.selectable owns the click so the whole row is one a11y unit. |
enabled | Boolean | true | When false, the option is dimmed (alpha) and non-interactive. |
colors | RadioButtonColors | RadioButtonDefaults.colors() | Brand override: selectedColor = accentPrimary, matching Switch/Checkbox. |
Modifier.selectable | selected, onClick, role | On the parent row: role = Role.RadioButton + the group wrapped in Modifier.selectableGroup() for roving focus + single-choice semantics. | |
interactionSource | MutableInteractionSource | remembered | Source for the ripple/press interactions (Android ripple, not hover). |
macOS has no radio control; single choice is the custom SegmentedSelector
(or a system Picker). State is two-way bound. There are no per-option state
parameters.
| Style | Type | Default | Description |
|---|---|---|---|
options | [Option] | The choices (Hashable & Identifiable), rendered left-to-right as equal-width segments. | |
selection | Binding<Option> | Two-way bound chosen option. The active segment gets a cyan outline (1.5pt), not a fill. | |
label | (Option) -> String | Maps an option to its visible text (e.g. { $0.label }). | |
.pickerStyle | PickerStyle | System stand-in for long lists: .inline (chosen row = checkmark) or .segmented. |