RadioGroup
Related choices with one selected value, descriptions and disabled options.
Playground and Examples use the shared browser runtime. For Service Studio inputs and event wiring, use Implementation and API.
Open OutSystems showcase ↗Implement RadioGroup in your application
Use the same setup, screen state and event mapping workflow as the existing components. These components are included in Community 1.1.0.
Related choices with one selected value, descriptions and disabled options. These components are included in @blukavantui/community 1.1.0.
Prepare your local Web project
Use a TypeScript application with a CSS-capable bundler. Install @blukavantui/community@1.1.0 from npm, or install the matching downloaded archive.
// In your test application’s package.json dependencies:
"@blukavantui/community": "1.1.0"Run npm install in that test application to install the component package. Load the theme once and the component stylesheet shown in the adapter. Create an empty host in your view:
<div id="example"></div>Mount one typed instance
This reusable adapter follows the Button and TimePicker guide pattern. Its types come from the factory, so required options stay required. Attach the host first; mount once per view and retain the handle.
import { createRadioGroup } from '@blukavantui/community/radio-group'
import '@blukavantui/community/theme.css'
import '@blukavantui/community/radio-group.css'
type Options = NonNullable<Parameters<typeof createRadioGroup>[1]>
type Events = NonNullable<Parameters<typeof createRadioGroup>[2]>
export function mount(host: HTMLElement, options: Options, events: Events = {}) {
const instance = createRadioGroup(host, options, events)
return { instance, update: (patch: Partial<Options>) => instance.update(patch), destroy: () => instance.destroy() }
}
Connect state and updates
User interaction calls Change; update() and reset() do not. Synchronize your application state after either method. Clear a stale Error before validate(), and validate again in your application before saving. These fields expose Change and Blur; TextInput also exposes Action.
The working example stores values in application variables. If the host is inside a form, a non-canceled form reset restores initial component values; synchronize your store after that reset and clear any application errors explicitly.
Bind one selected value to typed choices
Use: Options, Value, Direction, Change
Pass a Choice array directly. Use unique values and keep one selected string in application state. Direction affects presentation, while arrows and Tab follow radio semantics. Disable individual options in the array.
Choose by keyboard and pointer; confirm the disabled option cannot be selected.
Replace options and validate the result
Use: Options, Required, getValue, validate
update({Options}) recreates the radio inputs. Keep stable values; if you remove the selected option, read getValue() and synchronize your state. Empty choices provide no control for browser required validation, so check that an allowed selection exists in your application too.
Remove the selected option, confirm no stale selection remains, and require a new choice.
Use the complete working example
Save this as radio-group.ts, import its mount function and pass your attached host. It includes application state, interactive controls and a destroy handler.
Download the editable TypeScript example ↓
Complete RadioGroup example
import { createRadioGroup, type Choice } from '@blukavantui/community/radio-group'
import '@blukavantui/community/theme.css'
import '@blukavantui/community/radio-group.css'
export function mount(host: HTMLElement) {
const root = document.createElement('section')
const target = document.createElement('div')
const status = document.createElement('p'); status.setAttribute('role', 'status')
const replace = document.createElement('button')
replace.type = 'button'; replace.textContent = 'Remove email option'
const reset = document.createElement('button')
reset.type = 'button'; reset.textContent = 'Restore choices'
const check = document.createElement('button')
check.type = 'button'; check.textContent = 'Check selection'
root.append(target, status, replace, reset, check); host.append(root)
const choices: Choice[] = [
{ Value: 'email', Label: 'Email', Description: 'Send a written update.' },
{ Value: 'phone', Label: 'Phone', Description: 'Speak to our team.' },
{ Value: 'post', Label: 'Post', Disabled: true, Description: 'Currently unavailable.' },
]
let selected = 'email'
const render = () => { status.textContent = `Selected: ${selected || '(none)'}` }
const instance = createRadioGroup(target, {
Name: 'contactMethod', Label: 'Contact method', Required: true,
Options: choices, Value: selected, Direction: 'Vertical',
}, { Change(value) { selected = value; instance.update({ Error: '' }); render() } })
const listeners = new AbortController()
replace.addEventListener('click', () => {
instance.update({ Options: choices.filter(option => option.Value !== 'email') })
selected = instance.getValue(); render() // Updates do not emit Change.
}, { signal: listeners.signal })
reset.addEventListener('click', () => {
instance.update({ Options: choices, Error: '' }); instance.reset()
selected = instance.getValue(); render()
}, { signal: listeners.signal })
check.addEventListener('click', () => {
instance.update({ Error: '' })
selected = instance.getValue()
// Also reject an empty selection when no enabled choices remain.
const valid = !!selected && instance.validate()
instance.update({ Error: valid ? '' : 'Choose an available contact method.' })
status.textContent = valid ? `Ready: ${selected}` : 'A contact method is required.'
}, { signal: listeners.signal })
render()
return { instance, destroy() { listeners.abort(); instance.destroy(); root.remove() } }
}
Verify behavior and dispose
- Use Tab to reach the control. Space toggles choices; radio groups support arrow navigation. Inputs retain standard text-editing shortcuts.
- Check required, disabled and read-only states; verify reset and programmatic updates also synchronize your application state.
- Call the example handle’s
destroy()when the view is removed. Abort your application listeners and requests; ignore any asynchronous result after disposal. - Mount again and confirm there is one instance and one callback per interaction.
Reference the native producer
In Manage Dependencies, select RadioGroup from BluKavantUi_Community_Inputs, together with the structures and public actions used by its API. Refresh existing references before publishing your consumer. Inherit the shared Community theme in your application's theme.
Place the Block in your screen
ExampleScreen — Screen (your application)
└─ ExistingLayout — referenced Layout Block instance (child of ExampleScreen)
└─ Content — existing slot on ExistingLayout
└─ ComponentRegion — Container (child of that slot)
└─ MainComponent — BluKavantUi_Community_Inputs.RadioGroup Block instance (child of ComponentRegion)Reuse your screen’s existing Layout and its content slot; do not add a second layout. If your Layout names that slot differently, use its actual content slot. ComponentRegion is a new Container; MainComponent is a referenced Block instance.
Create and bind screen state
Create local variables on ExampleScreen for the values your application changes. Use the exact native types below; lists are typed OutSystems Lists, while inputs ending in Json are Text containing the documented JSON. Bind each MainComponent.Input to its matching StateInput local. Constants can instead use the default directly. Fill required data from your Aggregate or Data Action before enabling interaction.
| MainComponent input | Screen local / native type | Initial value | Purpose |
|---|---|---|---|
| Error | StateError · Text | "" | Application validation message; a non-empty value marks the control invalid. Clear it after validation succeeds. |
| Value | StateValue · Text | "" | Current Text value. Bind screen state and assign the OnChange.Value argument back to the same variable. |
| OptionsJson | StateOptionsJson · Text | "[]" | Text containing a JSON array of options. Each option needs a unique Value and Label; Description and Disabled are optional. Use JSON Serialize with your own option structure. |
All native input defaults
| Input | Type | Default | Required |
|---|---|---|---|
| Name | Text | "" | No |
| Label | Text | "" | No |
| Description | Text | "" | No |
| Error | Text | "" | No |
| Required | Boolean | False | No |
| Disabled | Boolean | False | No |
| ReadOnly | Boolean | False | No |
| Size | Text | "Medium" | No |
| ExtendedClass | Text | "" | No |
| Value | Text | "" | No |
| OptionsJson | Text | "[]" | No |
| Direction | Text | "Vertical" | No |
| EnableMotion | Boolean | False | No |
| MotionPreference | Text | "System" | No |
| UseDefaultMotionDuration | Boolean | True | No |
| MotionDurationMs | Integer | 450 | No |
| MotionEasing | Text | "standard" | No |
| EnableRipple | Boolean | False | No |
Create the event handlers and map every argument
For each event you use, create the named screen Client Action below. Add its listed input parameters with the exact types. Select that action on MainComponent's event property, then enter each mapping explicitly. Event arguments and screen locals are different scopes.
OnBlur → HandleBlur
Raised when a field loses focus. Validate your screen state here when using validation on blur.
| Handler input | Native type | Event mapping |
|---|
Flow: Start → run your application action using the mapped event arguments → End.
OnChange → HandleChange
Raised when the user changes the value. Assign the emitted Value back to the bound screen variable.
| Handler input | Native type | Event mapping |
|---|---|---|
| RequestedValue | Text | RequestedValue = Value |
Flow: Start → validate the requested value; assign the corresponding screen state so the Block receives it back → End.
OnError → HandleError
Reports native runtime initialization or update errors. Show safe feedback and inspect the configuration; this is not the normal field validation event.
| Handler input | Native type | Event mapping |
|---|---|---|
| RequestedMessage | Text | RequestedMessage = Message |
Flow: Start → display the mapped error argument in your feedback region → End.
Apply the example event flow
MainComponent.OnChange → HandleChange
Map RequestedValue = Value
Start
→ Assign StateValue = RequestedValue
→ EndPrepare the payloads
Inputs ending in Json are Text containing JSON. Define the matching application Structure/List, then use JSON Serialize. The JSON below is payload content, not an OutSystems expression literal.
OptionsJson
Serialize your own option List with these attribute names.
[
{
"Value": "standard",
"Label": "Standard",
"Description": "Regular service"
},
{
"Value": "priority",
"Label": "Priority",
"Disabled": false
}
]Validate before saving
These custom controls do not automatically participate in OutSystems Form.Valid. Validate your bound screen state, assign Error text for invalid fields, and call your own Server Action only when valid. Validate again on the server. Required is a runtime field requirement, not a mandatory Block input.
Verify in your consumer
Publish the consumer, supply representative data, trigger each connected event and inspect the mapped arguments. Check a failed request, a retry and leaving/reopening the screen. The table describes the native contract; the Web recipes can contain capabilities with different native names or availability. Use these native names and the API tab as the authority for Service Studio.
Shared component motion settings
Use createMotionComponents from the local bluui-community/src/component-motion.ts entry. The factories retain the component’s data, options, events and methods, and add setMotion(settings?). Create the controller with createMotionController from bluui-community/src/motion.ts.
| Setting / method | Behavior |
|---|---|
mode | inherit (default), reduced, or off. The owning controller and operating system can always reduce or disable motion. |
duration | Optional finite-transition override, 0–2000 ms. Omit for default timings; zero is immediate. Reduced caps permitted fades at 200 ms. Continuous loading retains its own cadence. |
easing | Optional standard, ease-in, ease-out, ease-in-out, linear or spring. Omit for each component's default easing. |
ui.configure(settings?) | Replace collection defaults, update existing instances and cancel their unfinished decoration. Explicit instance overrides remain. Omit settings to restore component defaults. |
component.setMotion(settings?) | Replace just this instance's overrides. Omit settings to inherit the collection again. Inputs, focus, content and actions are retained. |
ui.destroy() | Dispose owned instances and their motion before destroying the controller. No global listener is added at import time. |
Validation rejects nonfinite, negative and over-2000-ms durations before changing settings. Motion follows the existing component behavior. Grid rows need stable keys; charts update their actual data series; workflow transitions retain their approval and save contracts. Charts respect EnableAnimation: false; continuous Sparkline glow keeps its own cadence and Pause control. Ripple, glow, shimmer and page-entry helpers remain explicit additions.
Open the playground motion controls · Read the motion and OutSystems integration guide
Current playground settings
Open the playground to load the local source example.RadioGroup with application state
These components are included in @blukavantui/community 1.1.0. This is the same complete example used by the implementation guide, with real controls, event handling and cleanup.
import { createRadioGroup, type Choice } from '@blukavantui/community/radio-group'
import '@blukavantui/community/theme.css'
import '@blukavantui/community/radio-group.css'
export function mount(host: HTMLElement) {
const root = document.createElement('section')
const target = document.createElement('div')
const status = document.createElement('p'); status.setAttribute('role', 'status')
const replace = document.createElement('button')
replace.type = 'button'; replace.textContent = 'Remove email option'
const reset = document.createElement('button')
reset.type = 'button'; reset.textContent = 'Restore choices'
const check = document.createElement('button')
check.type = 'button'; check.textContent = 'Check selection'
root.append(target, status, replace, reset, check); host.append(root)
const choices: Choice[] = [
{ Value: 'email', Label: 'Email', Description: 'Send a written update.' },
{ Value: 'phone', Label: 'Phone', Description: 'Speak to our team.' },
{ Value: 'post', Label: 'Post', Disabled: true, Description: 'Currently unavailable.' },
]
let selected = 'email'
const render = () => { status.textContent = `Selected: ${selected || '(none)'}` }
const instance = createRadioGroup(target, {
Name: 'contactMethod', Label: 'Contact method', Required: true,
Options: choices, Value: selected, Direction: 'Vertical',
}, { Change(value) { selected = value; instance.update({ Error: '' }); render() } })
const listeners = new AbortController()
replace.addEventListener('click', () => {
instance.update({ Options: choices.filter(option => option.Value !== 'email') })
selected = instance.getValue(); render() // Updates do not emit Change.
}, { signal: listeners.signal })
reset.addEventListener('click', () => {
instance.update({ Options: choices, Error: '' }); instance.reset()
selected = instance.getValue(); render()
}, { signal: listeners.signal })
check.addEventListener('click', () => {
instance.update({ Error: '' })
selected = instance.getValue()
// Also reject an empty selection when no enabled choices remain.
const valid = !!selected && instance.validate()
instance.update({ Error: valid ? '' : 'Choose an available contact method.' })
status.textContent = valid ? `Ready: ${selected}` : 'A contact method is required.'
}, { signal: listeners.signal })
render()
return { instance, destroy() { listeners.abort(); instance.destroy(); root.remove() } }
}
Mount and dispose
Call mount after the host is attached to the document. Retain the returned handle and call destroy from your view’s unmount hook.
import { mount } from './radio-group'
const host = document.getElementById('example')
if (!host) throw new Error('Missing example host')
const view = mount(host)
// Register this with your framework/router’s unmount hook:
function disposeView() { view.destroy() }Use instance.update(patch) for changes while mounted; creating another instance on each render leaves duplicate controls.
Build with native Blocks
Configure the referenced Block in Service Studio using typed inputs, screen state and Client Actions. The Implementation tab includes the widget tree and exact event argument mappings.
Shared component motion settings
Use createMotionComponents from the local bluui-community/src/component-motion.ts entry. The factories retain the component’s data, options, events and methods, and add setMotion(settings?). Create the controller with createMotionController from bluui-community/src/motion.ts.
| Setting / method | Behavior |
|---|---|
mode | inherit (default), reduced, or off. The owning controller and operating system can always reduce or disable motion. |
duration | Optional finite-transition override, 0–2000 ms. Omit for default timings; zero is immediate. Reduced caps permitted fades at 200 ms. Continuous loading retains its own cadence. |
easing | Optional standard, ease-in, ease-out, ease-in-out, linear or spring. Omit for each component's default easing. |
ui.configure(settings?) | Replace collection defaults, update existing instances and cancel their unfinished decoration. Explicit instance overrides remain. Omit settings to restore component defaults. |
component.setMotion(settings?) | Replace just this instance's overrides. Omit settings to inherit the collection again. Inputs, focus, content and actions are retained. |
ui.destroy() | Dispose owned instances and their motion before destroying the controller. No global listener is added at import time. |
Validation rejects nonfinite, negative and over-2000-ms durations before changing settings. Motion follows the existing component behavior. Grid rows need stable keys; charts update their actual data series; workflow transitions retain their approval and save contracts. Charts respect EnableAnimation: false; continuous Sparkline glow keeps its own cadence and Pause control. Ripple, glow, shimmer and page-entry helpers remain explicit additions.
Open the playground motion controls · Read the motion and OutSystems integration guide
Current playground settings
Open the playground to load the local source example.createRadioGroup
User interaction calls Change; update() and reset() do not. Synchronize your application state after either method. Clear a stale Error before validate(), and validate again in your application before saving. These fields expose Change and Blur; TextInput also exposes Action.
import { createRadioGroup } from '@blukavantui/community/radio-group';
import '@blukavantui/community/radio-group.css';Load @blukavantui/community/theme.css once in your app shell.createRadioGroup(host: HTMLElement, initial: RadioGroupOptions, events?: FieldEvents<string>)These components are included in @blukavantui/community 1.1.0. Required/optional and signatures come from the 1.1.0 TypeScript source. Defaults below describe runtime behavior, which may differ from the Playground’s selected example.
Options
13| Name | Type / requirement | Usage |
|---|---|---|
Value | string | undefinedOptional | Default: "". Current text/choice value. The component keeps user changes internally; use Change to synchronize your own state and update({Value}) for application-driven changes. |
Options | Choice[]Required | Required. Typed Choice array; pass objects directly, not OptionsJson. Use unique Value strings. Replace the array with update({Options}) to change choices. RadioGroup rejects duplicate values synchronously; use non-empty values for an unambiguous empty selection. |
Direction | "Horizontal" | "Vertical" | undefinedOptional | Default: "Vertical". Horizontal or Vertical arrangement of RadioGroup options. |
Id | string | undefinedOptional | Default: Generated once. Control id used for label/help association. Set when mounting; changing Id later does not replace the generated identity. |
Name | string | undefinedOptional | Default: Generated once when omitted. Shared HTML radio name for this group. Supply a stable unique name for FormData; the generated fallback keeps unnamed groups independent. |
Label | string | undefinedOptional | Default: "". Visible accessible label. Placeholder text does not replace a label. |
Description | string | undefinedOptional | Default: "". Supporting text associated with the control through aria-describedby. |
Error | string | undefinedOptional | Default: "". Validation message and invalid appearance. Input components also set custom validity; clear Error before rechecking a corrected value. |
Required | boolean | undefinedOptional | Default: false. Enables the control’s required constraint and accessible required state. Validation still belongs to your application. |
Disabled | boolean | undefinedOptional | Default: false. Prevents interaction; disabled native form controls are omitted from FormData. |
ReadOnly | boolean | undefinedOptional | Default: false. Prevents user changes while retaining a readable value. Choice and Select controls implement this behavior themselves. |
Size | FieldSize | undefinedOptional | Default: "Medium". Small, Medium or Large. Uses the shared field sizing tokens. |
ExtendedClass | string | undefinedOptional | Default: "". Additional space-separated classes on the root. Keep application CSS scoped. |
Callbacks
2| Name | Type / requirement | Usage |
|---|---|---|
Change | ((value: string) => void) | undefinedOptional | Called for a user value change (including a clear action). TextInput/TextArea emit on input; choices emit on selection. update and reset do not emit Change. Synchronize your application state explicitly after programmatic changes. |
Blur | (() => void) | undefinedOptional | Called when the input loses focus. Select reports Blur when focus leaves its composite control. Use it to run application validation if desired. |
Instance methods & properties
7| Name | Signature / type | Usage |
|---|---|---|
element | HTMLFieldSetElement | HTMLDivElement | Root DOM element owned by the component. Mount into a real host before using focus or validation. |
update | (patch: Partial<RadioGroupOptions>) => void | Shallow-merge an options patch into the current configuration. Arrays/adornment objects replace their previous value. User-change callbacks are not emitted. Call before destroy; updates afterward throw. |
reset | () => void | Restore the initial Value/Checked (and Checkbox Indeterminate), retaining current options such as labels and choices. Does not emit Change or clear an application Error. A non-canceled form reset also invokes this when mounted inside that form. |
getValue | () => string | Read the current user value; Boolean for Checkbox/Switch/RadioButton, string for TextInput/TextArea/RadioGroup/Select. Use this after programmatic updates or reset to synchronize your state. |
focus | () => void | undefined | Focus the visible control. RadioGroup prefers the selected enabled option, then the first enabled option; Select focuses its custom trigger. |
validate | () => boolean | Calls browser reportValidity and returns a Boolean; it may focus the invalid control. RadioGroup checks its enabled inputs. Clear a stale Error first. There is no ValidationChanged callback on these new fields. With no enabled options it returns true; enforce an application-level allowed selection when required. |
destroy | () => void | Remove owned DOM and listeners. Destroy child component instances before their layout. Safe to call repeatedly; do not use other methods afterward. |
Related data types
Expand a type for its exact fields and allowed values. Pass objects and arrays directly in Web code.
RadioGroupOptions
Current local TypeScript declaration. Use the component-specific options and callbacks shown above.
interface RadioGroupOptions extends FieldOptions { Value?: string; Options: Choice[]; Direction?: 'Horizontal' | 'Vertical'; }FieldOptions
Common typed field options. FormField only applies presentation and ARIA; see its option notes for properties managed by the consumer.
interface FieldOptions {
Id?: string; Name?: string; Label?: string; Description?: string; Error?: string;
Required?: boolean; Disabled?: boolean; ReadOnly?: boolean; Size?: FieldSize;
ExtendedClass?: string;
}FieldSize
Allowed shared input sizes.
type FieldSize = 'Small' | 'Medium' | 'Large';Choice
One option shared by RadioGroup and Select. Value is stored; Label and optional Description are visible text. Disabled prevents user selection.
interface Choice { Value: string; Label: string; Description?: string; Disabled?: boolean; }FieldEvents
Current local TypeScript declaration. Use the component-specific options and callbacks shown above.
interface FieldEvents<T> { Change?: (value: T) => void; Blur?: () => void; }Verified native contract for BluKavantUi_Community_Inputs → Essentials → RadioGroup. Bindings, events and layout were verified in the compiled development consumer on 2026-10-24.
Browser checks cover labels, keyboard interactions, values and cleanup. Scoped Safari VoiceOver and browser checks passed; this is not a full-library conformance certification. Test your composed application with its actual content and assistive technologies.
Purpose and use
Related choices with one selected value, descriptions and disabled options.
Keyboard design guidance
Use Tab to reach the control. Space toggles choices; radio groups support arrow navigation. Inputs retain standard text-editing shortcuts.
See the support status above for review coverage and any limitations.
In your application
Provide useful labels, preserve visible focus, and test the component with your actual content. Use text alongside status colors and check contrast in each state.
Events0No events yet.
Interact with an example to see its events.