JGuang

ComplexSelector

ComplexSelector · Astryx

Preview
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

ComplexSelector

Use ComplexSelector when a selection needs richer custom content than a Selector option row. It is intentionally one component: ComplexSelector owns the field, trigger, popover, focus restore, and changeAction flow, while the content render prop owns the selector-specific accessible structure.

Installation

npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

Requires React 19+, base CSS and a Theme provider. See Setup.

Complex Selector

Use ComplexSelector when a selection needs richer custom content than a Selector option row. It is intentionally one component: ComplexSelector owns the field, trigger, popover, focus restore, and changeAction flow, while the content render prop owns the selector-specific accessible structure.

Usage guidance

  • Use variant="secondary" beside secondary buttons, or variant="ghost" beside ghost buttons; add a startIcon when it helps identify the trigger. Use alignment="end" when a wide surface should align its end edge to the trigger.
  • For staged editors, keep draft state in the composed content and call the provided onChange helper only from Apply. Cancel or dismiss without committing.
  • Compose the dialog content from the appropriate accessible structure for the job: RadioList for a simple choice, Calendar/date inputs for date picking, TreeList or a searchable list for hierarchy, or a custom grid when two-dimensional arrow navigation is useful.
  • Use the provided onChange helper from children; it already calls both onChange and changeAction and updates optimistic busy state.
  • Call close() from custom content when a selection should dismiss the popup. Keep it open for multi-step content or freeform entry flows.
  • Use Astryx focus hooks for custom content: useGridFocus for two-dimensional grids, useTreeFocus through TreeList for hierarchies, and useListFocus for custom linear collections.
  • Evaluate custom content against WCAG 2.2: keyboard operation, focus visible/not obscured, names and roles, labels/instructions, target size, and contrast/non-text contrast are especially relevant for selector popovers.
  • Avoid: Do not rebuild trigger ARIA, popover focus management, or changeAction handling in product code.
  • Avoid: Do not use ComplexSelector for a plain single-column text list; use Selector instead.

Theming API

{
  "targets": [
    {
      "className": "astryx-complex-selector",
      "visualProps": [
        "variant",
        "size",
        "status"
      ]
    },
    {
      "className": "astryx-complex-selector-indicator-icon",
      "states": [
        "state"
      ]
    },
    {
      "className": "astryx-complex-selector-popup"
    }
  ]
}

Complex Selector

An input or toolbar trigger and dialog-popover shell for custom selector content.

PropTypeDefaultDescription
label *stringLabel text for accessibility and the field label.
value *ValueCurrent controlled value.
onChange(value: Value) => voidCalled when custom content commits a new value.
changeAction(value: Value) => void | Promise<void>Async action after onChange. ComplexSelector exposes optimistic value and busy state while pending.
children *(value: Value, onChange: (value: Value) => void, close: () => void, state: ComplexSelectorRenderState) => ReactNodeCustom dialog content. Receives positional value, onChange, close, and state helpers.
triggerLabelReactNodeLabel/content shown in the closed trigger.
placeholderReactNode'Select...'Placeholder shown when triggerLabel is omitted.
isDisabledbooleanDisables the selector.
isLoadingbooleanShows loading state on the trigger.
status{type: 'warning' | 'error' | 'success', message?: string}Validation status.
statusVariant'attached' | 'detached' | 'tooltip''attached' for input selectors; 'detached' for secondary and ghost selectorsHow the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment; detached when variant is secondary or ghost); detached floats below as a separate element with spacing; tooltip reveals the message in an info-tip button.
size'sm' | 'md' | 'lg''md'Exact trigger height: sm 28px, md 32px, or lg 36px.
variant'input' | 'secondary' | 'ghost''input'Visual trigger style. Input is the bordered form treatment; secondary matches the secondary buttons of the active theme (borderless and neutral-filled by default); ghost is borderless and transparent to match ghost buttons.
startIconReactNode | IconTypeIcon displayed at the start of the trigger.
widthSizeValueWidth of the field.
placement'above' | 'below' | 'start' | 'end''below'Popup placement.
alignment'start' | 'center' | 'end''start'Popup alignment along the placement axis.
renderTrigger(props: ComplexSelectorRenderTriggerProps) => ReactNodeRender the control the popup hangs off — a glyph in a list row, a chip, an icon button — instead of the selector's own field and button. Spread the given props ({ref, id, onClick, onKeyDown, aria-haspopup, aria-expanded, aria-controls, aria-busy}) onto it; the popup is anchored to that control and still labelled by label. The field chrome is not rendered. Pair with handleRef to open from a keystroke elsewhere.
handleRefReact.Ref<ComplexSelectorHandle>Imperative handle for programmatic control. Exposes open(), close(), toggle(), and isOpen().
onOpenChange(isOpen: boolean) => voidCalled whenever the surface opens or closes, however it happened — trigger, keyboard, light dismiss, Escape, close(), or the imperative handle.
contentXstyleStyleXStylesStyleX styles for the popup content container.
onClick(event: MouseEvent) => voidClick handler on the trigger. Runs before the popup opens; call preventDefault() to keep it closed. A press elsewhere in the control box goes through the trigger too. With renderTrigger it runs after the built-in toggle and cannot cancel it.
onKeyDown(event: KeyboardEvent) => voidKey handler on the trigger. Runs after the built-in handling and receives every key, including the ArrowDown that opens the popup. With renderTrigger it is skipped for keys the built-in handling cancels.
containerXstyleStyleXStylesStyleX styles for the field container (label, control, and status together). No effect inside InputGroup (style the InputGroup instead) or in a horizontal-labels FormLayout. Root xstyle, className, and style are not accepted.
containerClassNamestringClass name for the field container. Same targeting as containerXstyle.
containerStyleCSSPropertiesInline styles for the field container. Same targeting as containerXstyle.
controlXstyleStyleXStylesStyleX styles for the visual control box: the element that paints the border and background and holds the value, icons, and end controls. Never the trigger button itself.
controlClassNamestringClass name for the visual control box.
controlStyleCSSPropertiesInline styles for the visual control box.

Anatomy

  • Field: Field shell that provides the label and optional supporting field content.
  • Trigger: Control that displays the current value or placeholder and opens the popup.
  • Icon-rendered start icon: Optional leading semantic icon or icon component rendered through Icon.
  • Caller-rendered start content: Optional arbitrary React content rendered directly at the start of the trigger.
  • Indicator icon: Trailing chevron that rotates to reflect whether the popup is open.
  • Popup: Mounted dialog surface that is painted and shown while open and hidden while closed.

Examples

  • Complex Selector: A two-axis picker: choose a fruit and a ripeness level from one control. ComplexSelector owns the trigger, popover, and focus restore while the custom grid owns its keyboard semantics.
  • Complex Selector — Deadline Picker: A multi-step deadline field: pick a preset like Today or Next week, or switch to a custom date and time before applying. The popup stays open until the user commits, so the content owns the Apply action.
  • Complex Selector — Tree Search: A destination picker that combines a search field with a TreeList hierarchy. TreeList owns tree keyboard navigation; ComplexSelector owns the trigger, popover, and focus restore. Selecting a folder closes the popup.

Source StyleX compilation

ComplexSelectorShowcase contain stylex.create and require a StyleX source build pipeline. Precompiled Core CSS alone does not compile these examples. See Styling before copying them.