Skip to main content
cngx-src documentation

CngxButtonToggleGroup

cngx-button-toggle-group

ComponentPrimaryOnPushNo encapsulationv0.1.0WCAG AA

projects/common/interactive/button-toggle/button-toggle-group.component.ts

Import#

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

Description#

Single-select button-toggle group. Owns a value = model<T | undefined> (the canonical single-value source) and exposes the parent contract via CNGX_BUTTON_TOGGLE_GROUP. Behaves as a W3C APG radiogroup - arrow keys move focus AND select (auto-select variant); Tab + programmatic focus do not select on focus alone; Space and Enter select the currently-focused toggle.

Mode is static: this component is the single-select half of a deliberate split (per feedback_select_family_split). Consumers pick <cngx-button-toggle-group> for radiogroup semantics or <cngx-button-multi-toggle-group> for toolbar semantics - never a runtime [selectionMode] flag, never a shape-shifter. The leaf CngxButtonToggle injects EITHER this token OR CNGX_BUTTON_MULTI_TOGGLE_GROUP ({ optional: true } on both) and chooses its ARIA pattern at injection time, not at runtime.

Auto-select wiring follows the same contract as CngxRadioGroup: the host CngxRovingTabindex raises its navigation-key intent before it moves focus; each focused leaf consumes that intent in its (focus) handler via consumePendingArrowSelect(), which reads roving.consumeNavigationKey(). Reading an already-set fact (instead of a group-owned (keydown) flag) closes the one-press-behind race (#135). The value write happens inside a DOM event handler - never inside an effect().

<cngx-button-toggle-group label="Layout" [(value)]="view">
  <button cngxButtonToggle value="grid">Grid</button>
  <button cngxButtonToggle value="list">List</button>
  <button cngxButtonToggle value="table">Table</button>
</cngx-button-toggle-group>

https://cngxjs.github.io/cngx/examples/#/common/interactive/button-toggle/group/basic-view-switcher https://cngxjs.github.io/cngx/examples/#/common/interactive/button-toggle/group/disabled-group-cascade-vs-per-toggle https://cngxjs.github.io/cngx/examples/#/common/interactive/button-toggle/group/vertical-orientation

Metadata#

Host#

Providers#

CNGX_BUTTON_TOGGLE_GROUP
useExisting CngxButtonToggleGroup
CNGX_CONTROL_VALUE
useExisting CngxButtonToggleGroup
CNGX_FORM_FIELD_CONTROL
useExisting CngxButtonToggleGroup

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()
orientation#"horizontal" | "vertical"
input()
default 'horizontal'
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-button-toggle-group-')).asReadonly()

Methods#

consumePendingArrowSelect#boolean
consumePendingArrowSelect(value: T)
@paramvalueT
handleFocusIn#void
Protected
handleFocusOut#void
Protected

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-button-toggle-group--horizontal]orientation() === "horizontal"

HostListeners#

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

Default visuals for the shared button-toggle group container. The host carries either .cngx-button-toggle-group (single-select CngxButtonToggleGroup) or .cngx-button-multi-toggle-group (multi-select CngxButtonMultiToggleGroup). Children sit flush so the row reads as a segmented control.

Variants

  • --horizontal - flex-direction row (the default); a :not() rule on the scope root flips to column when this class is absent

Inheritance

Layout tokens cascade through the foundation:

  • --cngx-button-toggle-group-gap - direct (defaults to 0)
  • --cngx-button-toggle-group-radius -> --cngx-radius-md

Index#

Layout

--cngx-button-toggle-group-gap#*
Default value 0

Gap between toggle children. Zero by default so the buttons read as a segmented control.

--cngx-button-toggle-group-radius#*
Default value 0.375rem

Corner radius of the group container. Defaults to --cngx-radius-md.