Skip to main content
cngx-src documentation

CngxRadioGroup

cngx-radio-group

ComponentPrimaryOnPushNo encapsulationv0.1.0WCAG AA

projects/common/interactive/radio/radio-group.component.ts

Import#

import { CngxRadioGroup } from '@cngx/common/interactive'

Description#

Radio-group molecule that owns the canonical value for a set of CngxRadio leaves and provides the parent contract via CNGX_RADIO_GROUP. The group composes CngxRovingTabindex as a host directive (with inputs: ['orientation'] exposed) so arrow- key navigation across radios is wired from day one. Leaf registration is imperative - register() / unregister() keep a Map in declaration order so the group can resolve the active radio by id without re-querying content children on every change detection cycle.

Per W3C ARIA radio-group authoring practice, Tab enters the group; arrow keys move focus AND select (auto-select variant); Space/Enter also select the currently-focused radio. The group's value is the source of truth; each leaf computes radioChecked = computed(() => group.value() === radio.value()) against its own value input.

Auto-select wiring (no signal-write-in-effect). Per the pillar §6 hard rule, the group does NOT subscribe to CngxRovingTabindex.activeIndex via an effect that writes value. Instead, the host CngxRovingTabindex raises its navigation-key intent before it moves focus; the newly-focused leaf's (focus) handler calls group.consumePendingArrowSelect(this.value()), which reads roving.consumeNavigationKey() and writes value (a signal write inside a DOM event handler, not an effect - legal under the pillar rules). Reading an already-set fact instead of a group-owned (keydown) flag closes the one-press-behind race (#135). Tab-into-group fires focus without a preceding navigation key, so the hook reads false and no auto-select happens - Tab leaves the consumer's value untouched.

<cngx-radio-group [(value)]="payment" name="payment-method">
  <cngx-radio value="card">Card</cngx-radio>
  <cngx-radio value="cash">Cash</cngx-radio>
  <cngx-radio value="invoice" disabled>Invoice</cngx-radio>
</cngx-radio-group>

https://cngxjs.github.io/cngx/examples/#/common/interactive/radio/basic-vertical-group https://cngxjs.github.io/cngx/examples/#/common/interactive/radio/custom-dot-glyph https://cngxjs.github.io/cngx/examples/#/common/interactive/radio/disabled-group-cascades-per-radio-overrides https://cngxjs.github.io/cngx/examples/#/common/interactive/radio/orientation-horizontal

Metadata#

Host#

Providers#

CNGX_RADIO_GROUP
useExisting CngxRadioGroup
CNGX_CONTROL_VALUE
useExisting CngxRadioGroup
CNGX_FORM_FIELD_CONTROL
useExisting CngxRadioGroup

Relationships

Index#

Inputs#

disabled#boolean
model()
default false
errorMessageId#string | null
input()

Optional id of an external error message element. When set, the host emits aria-errormessage="<id>"; consumers MUST render an element with that id. Default null skips the attribute. Note: WAI-ARIA dictates that AT ignores this attribute when aria-invalid is absent or "false", so a stable always-emitted id is harmless when the field is valid.

default null
invalid#boolean
model()

Bridge-writable invalid state. model<boolean> mirrors disabled so external integrations (RF/Signal-Forms bridges, custom validity adapters) can drive it without a parallel API path - consumers typically read only.

default false
label#string | undefined
input()
nameInput#string | undefined
input()
default undefined, { alias: 'name' }
orientation#"horizontal" | "vertical"
input()
default 'vertical'
required#boolean
model()
default false
value#T | undefined
model()

Outputs#

disabled#boolean
model()
invalid#boolean
model()

Bridge-writable invalid state. model<boolean> mirrors disabled so external integrations (RF/Signal-Forms bridges, custom validity adapters) can drive it without a parallel API path - consumers typically read only.

required#boolean
model()
value#T | undefined
model()

Instance Properties#

focused#unknown
Readonly
this.focusedState.asReadonly()
id#unknown
signal()Readonly
signal(nextUid('cngx-radio-group-')).asReadonly()

Methods#

consumePendingArrowSelect#boolean
consumePendingArrowSelect(value: T)
@paramvalueT
handleFocusIn#void
Protected
handleFocusOut#void
Protected
register#void
register(radio: CngxRadioRegistration<T>)
unregister#void
unregister(id: string)
@paramidstring

HostBindings#

BindingExpression
[attr.id]id()
[attr.aria-label]label()
[attr.aria-disabled]disabled() ? "true" : null
[attr.aria-required]required() ? "true" : null
[attr.aria-invalid](invalid() || errorState()) ? "true" : null
[attr.aria-errormessage]errorMessageId()
[attr.aria-orientation]orientation()
[attr.aria-busy]ariaBusy() ? "true" : null
[class.cngx-radio-group--horizontal]orientation() === "horizontal"

HostListeners#

EventHandler
(focusin)focusin()
(focusout)focusout()

Default visuals for CngxRadioGroup. The host carries .cngx-radio-group and frames a flex layout for projected <cngx-radio> children. Stacks vertically by default. The scope root pins vertical-align: top so the host doesn't shift when the first leaf's indicator paints / unpaints its dot (the dot's appearance changes the inline-flex baseline by ~2px).

Variants

  • --horizontal - flips to a row (no wrap, so consumers control overflow explicitly)

Inheritance

Gap cascades through the foundation:

  • --cngx-radio-group-gap -> --cngx-space-sm

Layout

--cngx-radio-group-gap#*
Default value 0.5rem

Gap between radio children. Falls back to --cngx-space-sm.