Skip to content

Multi-check Group

MultiCheckGroup extends the checkbox-group interaction model to an ordered set of states per item. Instead of a boolean, every row stores a numeric state index in a two-way Signal<number[]>. Pressing a row advances its index and wraps from the final state back to the first.

This is useful for compact inheritance, coverage, or tri-state settings such as Off, Partial, and Full. It is not a substitute for a menu when state order has no clear meaning.

Usage

ts
import { MultiCheckGroup, signal } from '@jsvision/ui';

const coverage = signal([0, 2]);
const group = new MultiCheckGroup({
  items: ['~S~ync', '~B~ackup'],
  states: ' xX',
  value: coverage,
});

Live example

Cycle Off, Partial, and Full states independently; watch the last state wrap to the first.

The laboratory translates numeric state indexes into readable words beside the glyphs. It also includes a disabled row and a reset action so state boundaries remain easy to inspect.

Props

MultiCheckGroup accepts MultiCheckGroupOptions:

PropTypePurpose
itemsreadonly string[]One caption per row; each may mark a ~X~ item accelerator.
statesstringOrdered one-code-unit, one-cell marker glyphs; each defines one state.
valueSignal<number[]>Two-way state index per item, in the same positional order.

Provide at least one state glyph. Missing array entries read as state 0; an out-of-range value also renders as the first marker. The next press normalizes the written array and advances with wrapping, including from a negative external value.

Rows inherit setItemEnabled(index, enabled) from the shared cluster control.

Size and Layout

The group paints one row per item, using five cells for the bracketed marker before the caption. Allocate items.length rows and enough width for the longest display caption. A smaller height clips rows and does not introduce scrolling.

Use one UTF-16 code unit and one terminal display cell per marker, such as a space, x, or X. Astral emoji, wide glyphs, and combining sequences are not valid state markers because marker lookup is positional within the JavaScript string and the drawing column is exactly one cell.

State cycles

The glyph order is the interaction order. With states: ' xX', index 0 draws a blank marker, index 1 draws x, and index 2 draws X; another press returns to 0. Each row advances independently and writes a full-length number array.

External signal writes repaint all markers. Keep a separate semantic label table in application code when the numeric indexes need to be shown to users or persisted as domain values.

Keyboard & mouse

InputResult
Up / DownMove and wrap the highlight, skipping disabled rows.
SpaceAdvance the highlighted row by one state.
Click an enabled rowFocus, highlight, and advance that row.
Alt + item hotkeyFocus, highlight, and advance the matching row dialog-wide.

Disabled rows remain visible but cannot advance. Navigation is a handled no-op when every item is disabled.

Best Practices

  • Use a stable, documented state order. Users should be able to predict what the next press means.
  • Pair unfamiliar marker glyphs with a visible legend or reactive text, as the live example does.
  • Keep items, persisted indexes, and state labels aligned when reordering data.
  • Prefer CheckGroup for ordinary booleans and RadioGroup for one selection across rows.

Theming

Theme roleRegion
clusterNormalEnabled rows outside the current keyboard highlight.
clusterSelectedHighlighted row while the group owns focus.
clusterShortcutMarked accelerator glyph on enabled rows.
clusterDisabledDisabled marker, caption, and accelerator.

Custom state glyphs inherit the row's role rather than receiving state-specific colors. Choose glyphs that remain distinguishable in every cluster role and under limited terminal capabilities.