feat: Add support for multi-select ComboBox#9525
Open
devongovett wants to merge 9 commits intomainfrom
Open
Conversation
|
Build successful! 🎉 |
nwidynski
reviewed
Jan 27, 2026
|
Build successful! 🎉 |
|
Super excited for this! |
…ct-combobox # Conflicts: # packages/@react-spectrum/s2/src/ComboBox.tsx
|
Build successful! 🎉 |
## API Changes
react-aria-components/react-aria-components:ComboBox-ComboBox <T extends {}> {
+ComboBox <M extends SelectionMode = 'single', T extends {}> {
allowsCustomValue?: boolean
allowsEmptyCollection?: boolean
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
autoFocus?: boolean
children?: ChildrenOrFunction<ComboBoxRenderProps>
className?: ClassNameOrFunction<ComboBoxRenderProps> = 'react-aria-ComboBox'
defaultFilter?: (string, string) => boolean
defaultInputValue?: string
defaultItems?: Iterable<T>
- defaultSelectedKey?: Key
+ defaultValue?: ValueType<SelectionMode>
disabledKeys?: Iterable<Key>
form?: string
formValue?: 'text' | 'key' = 'key'
id?: string
inputValue?: string
isDisabled?: boolean
isInvalid?: boolean
isReadOnly?: boolean
isRequired?: boolean
items?: Iterable<T>
menuTrigger?: MenuTriggerAction = 'input'
name?: string
onBlur?: (FocusEvent<HTMLInputElement>) => void
+ onChange?: (T) => void
onFocus?: (FocusEvent<HTMLInputElement>) => void
onFocusChange?: (boolean) => void
onInputChange?: (string) => void
onKeyDown?: (KeyboardEvent) => void
onKeyUp?: (KeyboardEvent) => void
onOpenChange?: (boolean, MenuTriggerAction) => void
- onSelectionChange?: (Key | null) => void
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, ComboBoxRenderProps>
- selectedKey?: Key | null
+ selectionMode?: SelectionMode = 'single'
shouldFocusWrap?: boolean
slot?: string | null
style?: StyleOrFunction<ComboBoxRenderProps>
validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
validationBehavior?: 'native' | 'aria' = 'native'
+ value?: ValueType<SelectionMode>
}/react-aria-components:ComboBoxProps-ComboBoxProps <T extends {}> {
+ComboBoxProps <M extends SelectionMode = 'single', T extends {}> {
allowsCustomValue?: boolean
allowsEmptyCollection?: boolean
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
autoFocus?: boolean
children?: ChildrenOrFunction<ComboBoxRenderProps>
className?: ClassNameOrFunction<ComboBoxRenderProps> = 'react-aria-ComboBox'
defaultFilter?: (string, string) => boolean
defaultInputValue?: string
defaultItems?: Iterable<T>
- defaultSelectedKey?: Key
+ defaultValue?: ValueType<SelectionMode>
disabledKeys?: Iterable<Key>
form?: string
formValue?: 'text' | 'key' = 'key'
id?: string
inputValue?: string
isDisabled?: boolean
isInvalid?: boolean
isReadOnly?: boolean
isRequired?: boolean
items?: Iterable<T>
menuTrigger?: MenuTriggerAction = 'input'
name?: string
onBlur?: (FocusEvent<HTMLInputElement>) => void
+ onChange?: (T) => void
onFocus?: (FocusEvent<HTMLInputElement>) => void
onFocusChange?: (boolean) => void
onInputChange?: (string) => void
onKeyDown?: (KeyboardEvent) => void
onKeyUp?: (KeyboardEvent) => void
onOpenChange?: (boolean, MenuTriggerAction) => void
- onSelectionChange?: (Key | null) => void
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, ComboBoxRenderProps>
- selectedKey?: Key | null
+ selectionMode?: SelectionMode = 'single'
shouldFocusWrap?: boolean
slot?: string | null
style?: StyleOrFunction<ComboBoxRenderProps>
validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
validationBehavior?: 'native' | 'aria' = 'native'
+ value?: ValueType<SelectionMode>
}/react-aria-components:ComboBoxState-ComboBoxState <T> {
+ComboBoxState <M extends SelectionMode = 'single', T> {
close: () => void
collection: Collection<Node<T>>
commit: () => void
commitValidation: () => void
defaultInputValue: string
- defaultSelectedKey: Key | null
+ defaultValue: ValueType<SelectionMode>
disabledKeys: Set<Key>
displayValidation: ValidationResult
focusStrategy: FocusStrategy | null
inputValue: string
isFocused: boolean
isOpen: boolean
open: (FocusStrategy | null, MenuTriggerAction) => void
realtimeValidation: ValidationResult
resetValidation: () => void
revert: () => void
- selectedItem: Node<T> | null
- selectedKey: Key | null
+ selectedItems: Array<Node<T>>
selectionManager: SelectionManager
setFocused: (boolean) => void
setInputValue: (string) => void
setOpen: (boolean) => void
- setSelectedKey: (Key | null) => void
+ setValue: (Key | readonly Array<Key> | null) => void
toggle: (FocusStrategy | null, MenuTriggerAction) => void
updateValidation: (ValidationResult) => void
+ value: ValueType<SelectionMode>
}@react-aria/autocomplete/@react-aria/autocomplete:AriaSearchAutocompleteOptions AriaSearchAutocompleteOptions <T> {
aria-activedescendant?: string
aria-autocomplete?: 'none' | 'inline' | 'list' | 'both'
aria-controls?: string
aria-describedby?: string
aria-details?: string
aria-errormessage?: string
aria-haspopup?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
aria-label?: string
aria-labelledby?: string
autoComplete?: string
autoCorrect?: string
autoFocus?: boolean
children: CollectionChildren<T>
defaultInputValue?: string
defaultItems?: Iterable<T>
description?: ReactNode
disabledKeys?: Iterable<Key>
enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
errorMessage?: ReactNode | (ValidationResult) => ReactNode
excludeFromTabOrder?: boolean
form?: string
id?: string
inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
inputRef: RefObject<HTMLInputElement | null>
inputValue?: string
isDisabled?: boolean
isInvalid?: boolean
isReadOnly?: boolean
isRequired?: boolean
items?: Iterable<T>
keyboardDelegate?: KeyboardDelegate
label?: ReactNode
layoutDelegate?: LayoutDelegate
listBoxRef: RefObject<HTMLElement | null>
maxLength?: number
menuTrigger?: MenuTriggerAction = 'input'
minLength?: number
name?: string
onBeforeInput?: FormEventHandler<HTMLInputElement>
onBlur?: (FocusEvent<T>) => void
- onChange?: (T) => void
onClear?: () => void
onCompositionEnd?: CompositionEventHandler<HTMLInputElement>
onCompositionStart?: CompositionEventHandler<HTMLInputElement>
onCompositionUpdate?: CompositionEventHandler<HTMLInputElement>
onCut?: ClipboardEventHandler<HTMLInputElement>
onFocus?: (FocusEvent<T>) => void
onFocusChange?: (boolean) => void
onInput?: FormEventHandler<HTMLInputElement>
onInputChange?: (string) => void
onKeyDown?: (KeyboardEvent) => void
onKeyUp?: (KeyboardEvent) => void
onOpenChange?: (boolean, MenuTriggerAction) => void
onPaste?: ClipboardEventHandler<HTMLInputElement>
onSelect?: ReactEventHandler<HTMLInputElement>
onSubmit?: (string | null, Key | null) => void
pattern?: string
placeholder?: string
popoverRef: RefObject<HTMLDivElement | null>
spellCheck?: string
type?: 'text' | 'search' | 'url' | 'tel' | 'email' | 'password' | (string & {
}) = 'search'
validate?: (string) => ValidationError | boolean | null | undefined
validationBehavior?: 'aria' | 'native' = 'aria'
}/@react-aria/autocomplete:AriaSearchAutocompleteProps AriaSearchAutocompleteProps <T> {
aria-activedescendant?: string
aria-autocomplete?: 'none' | 'inline' | 'list' | 'both'
aria-controls?: string
aria-describedby?: string
aria-details?: string
aria-errormessage?: string
aria-haspopup?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
aria-label?: string
aria-labelledby?: string
autoComplete?: string
autoCorrect?: string
autoFocus?: boolean
children: CollectionChildren<T>
defaultInputValue?: string
defaultItems?: Iterable<T>
description?: ReactNode
disabledKeys?: Iterable<Key>
enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
errorMessage?: ReactNode | (ValidationResult) => ReactNode
excludeFromTabOrder?: boolean
form?: string
id?: string
inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
inputValue?: string
isDisabled?: boolean
isInvalid?: boolean
isReadOnly?: boolean
isRequired?: boolean
items?: Iterable<T>
label?: ReactNode
maxLength?: number
menuTrigger?: MenuTriggerAction = 'input'
minLength?: number
name?: string
onBeforeInput?: FormEventHandler<HTMLInputElement>
onBlur?: (FocusEvent<T>) => void
- onChange?: (T) => void
onClear?: () => void
onCompositionEnd?: CompositionEventHandler<HTMLInputElement>
onCompositionStart?: CompositionEventHandler<HTMLInputElement>
onCompositionUpdate?: CompositionEventHandler<HTMLInputElement>
onCut?: ClipboardEventHandler<HTMLInputElement>
onFocus?: (FocusEvent<T>) => void
onFocusChange?: (boolean) => void
onInput?: FormEventHandler<HTMLInputElement>
onInputChange?: (string) => void
onKeyDown?: (KeyboardEvent) => void
onKeyUp?: (KeyboardEvent) => void
onOpenChange?: (boolean, MenuTriggerAction) => void
onPaste?: ClipboardEventHandler<HTMLInputElement>
onSelect?: ReactEventHandler<HTMLInputElement>
onSubmit?: (string | null, Key | null) => void
pattern?: string
placeholder?: string
spellCheck?: string
type?: 'text' | 'search' | 'url' | 'tel' | 'email' | 'password' | (string & {
}) = 'search'
validate?: (string) => ValidationError | boolean | null | undefined
validationBehavior?: 'aria' | 'native' = 'aria'
}@react-aria/combobox/@react-aria/combobox:useComboBox-useComboBox <T> {
+useComboBox <M extends SelectionMode = 'single', T> {
- props: AriaComboBoxOptions<T>
- state: ComboBoxState<T>
+ props: AriaComboBoxOptions<T, M>
+ state: ComboBoxState<T, M>
returnVal: undefined
}/@react-aria/combobox:AriaComboBoxOptions-AriaComboBoxOptions <T> {
+AriaComboBoxOptions <M extends SelectionMode = 'single', T> {
allowsCustomValue?: boolean
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
autoFocus?: boolean
buttonRef?: RefObject<Element | null>
defaultInputValue?: string
defaultItems?: Iterable<T>
- defaultSelectedKey?: Key
+ defaultValue?: ValueType<SelectionMode>
description?: ReactNode
disabledKeys?: Iterable<Key>
errorMessage?: ReactNode | (ValidationResult) => ReactNode
form?: string
id?: string
inputRef: RefObject<HTMLInputElement | null>
inputValue?: string
isDisabled?: boolean
isInvalid?: boolean
isReadOnly?: boolean
isRequired?: boolean
items?: Iterable<T>
keyboardDelegate?: KeyboardDelegate
label?: ReactNode
layoutDelegate?: LayoutDelegate
listBoxRef: RefObject<HTMLElement | null>
menuTrigger?: MenuTriggerAction = 'input'
name?: string
onBlur?: (FocusEvent<HTMLInputElement>) => void
+ onChange?: (T) => void
onFocus?: (FocusEvent<HTMLInputElement>) => void
onFocusChange?: (boolean) => void
onInputChange?: (string) => void
onKeyDown?: (KeyboardEvent) => void
onKeyUp?: (KeyboardEvent) => void
onOpenChange?: (boolean, MenuTriggerAction) => void
- onSelectionChange?: (Key | null) => void
placeholder?: string
popoverRef: RefObject<Element | null>
- selectedKey?: Key | null
+ selectionMode?: SelectionMode = 'single'
shouldFocusWrap?: boolean
validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
validationBehavior?: 'aria' | 'native' = 'aria'
+ value?: ValueType<SelectionMode>
}/@react-aria/combobox:AriaComboBoxProps-AriaComboBoxProps <T> {
+AriaComboBoxProps <M extends SelectionMode = 'single', T> {
allowsCustomValue?: boolean
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
autoFocus?: boolean
children: CollectionChildren<T>
defaultInputValue?: string
defaultItems?: Iterable<T>
- defaultSelectedKey?: Key
+ defaultValue?: ValueType<SelectionMode>
description?: ReactNode
disabledKeys?: Iterable<Key>
errorMessage?: ReactNode | (ValidationResult) => ReactNode
form?: string
id?: string
inputValue?: string
isDisabled?: boolean
isInvalid?: boolean
isReadOnly?: boolean
isRequired?: boolean
items?: Iterable<T>
label?: ReactNode
menuTrigger?: MenuTriggerAction = 'input'
name?: string
onBlur?: (FocusEvent<HTMLInputElement>) => void
+ onChange?: (T) => void
onFocus?: (FocusEvent<HTMLInputElement>) => void
onFocusChange?: (boolean) => void
onInputChange?: (string) => void
onKeyDown?: (KeyboardEvent) => void
onKeyUp?: (KeyboardEvent) => void
onOpenChange?: (boolean, MenuTriggerAction) => void
- onSelectionChange?: (Key | null) => void
placeholder?: string
- selectedKey?: Key | null
+ selectionMode?: SelectionMode = 'single'
shouldFocusWrap?: boolean
validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
validationBehavior?: 'aria' | 'native' = 'aria'
+ value?: ValueType<SelectionMode>
}@react-spectrum/autocomplete/@react-spectrum/autocomplete:SearchAutocomplete SearchAutocomplete <T extends {}> {
UNSAFE_className?: string
UNSAFE_style?: CSSProperties
align?: 'start' | 'end' = 'start'
alignSelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>
aria-activedescendant?: string
aria-autocomplete?: 'none' | 'inline' | 'list' | 'both'
aria-controls?: string
aria-describedby?: string
aria-details?: string
aria-errormessage?: string
aria-haspopup?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
aria-label?: string
aria-labelledby?: string
autoComplete?: string
autoCorrect?: string
autoFocus?: boolean
bottom?: Responsive<DimensionValue>
children: CollectionChildren<{}>
contextualHelp?: ReactNode
defaultInputValue?: string
defaultItems?: Iterable<{}>
description?: ReactNode
direction?: 'bottom' | 'top' = 'bottom'
disabledKeys?: Iterable<Key>
end?: Responsive<DimensionValue>
enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
errorMessage?: ReactNode | (ValidationResult) => ReactNode
excludeFromTabOrder?: boolean
flex?: Responsive<string | number | boolean>
flexBasis?: Responsive<number | string>
flexGrow?: Responsive<number>
flexShrink?: Responsive<number>
form?: string
gridArea?: Responsive<string>
gridColumn?: Responsive<string>
gridColumnEnd?: Responsive<string>
gridColumnStart?: Responsive<string>
gridRow?: Responsive<string>
gridRowEnd?: Responsive<string>
gridRowStart?: Responsive<string>
height?: Responsive<DimensionValue>
icon?: ReactElement | null
id?: string
inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
inputValue?: string
isDisabled?: boolean
isHidden?: Responsive<boolean>
isQuiet?: boolean
isReadOnly?: boolean
isRequired?: boolean
items?: Iterable<{}>
justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
label?: ReactNode
labelAlign?: Alignment = 'start'
labelPosition?: LabelPosition = 'top'
left?: Responsive<DimensionValue>
loadingState?: LoadingState
margin?: Responsive<DimensionValue>
marginBottom?: Responsive<DimensionValue>
marginEnd?: Responsive<DimensionValue>
marginStart?: Responsive<DimensionValue>
marginTop?: Responsive<DimensionValue>
marginX?: Responsive<DimensionValue>
marginY?: Responsive<DimensionValue>
maxHeight?: Responsive<DimensionValue>
maxLength?: number
maxWidth?: Responsive<DimensionValue>
menuTrigger?: MenuTriggerAction = 'input'
menuWidth?: DimensionValue
minHeight?: Responsive<DimensionValue>
minLength?: number
minWidth?: Responsive<DimensionValue>
name?: string
necessityIndicator?: NecessityIndicator = 'icon'
onBeforeInput?: FormEventHandler<HTMLInputElement>
onBlur?: (FocusEvent<{}>) => void
- onChange?: ({}) => void
onClear?: () => void
onCompositionEnd?: CompositionEventHandler<HTMLInputElement>
onCompositionStart?: CompositionEventHandler<HTMLInputElement>
onCompositionUpdate?: CompositionEventHandler<HTMLInputElement>
onCut?: ClipboardEventHandler<HTMLInputElement>
onFocus?: (FocusEvent<{}>) => void
onFocusChange?: (boolean) => void
onInput?: FormEventHandler<HTMLInputElement>
onInputChange?: (string) => void
onKeyDown?: (KeyboardEvent) => void
onKeyUp?: (KeyboardEvent) => void
onLoadMore?: () => void
onOpenChange?: (boolean, MenuTriggerAction) => void
onPaste?: ClipboardEventHandler<HTMLInputElement>
onSelect?: ReactEventHandler<HTMLInputElement>
onSubmit?: (string | null, Key | null) => void
order?: Responsive<number>
pattern?: string
position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
right?: Responsive<DimensionValue>
shouldFlip?: boolean = true
spellCheck?: string
start?: Responsive<DimensionValue>
top?: Responsive<DimensionValue>
type?: 'text' | 'search' | 'url' | 'tel' | 'email' | 'password' | (string & {
}) = 'search'
validate?: (string) => ValidationError | boolean | null | undefined
validationBehavior?: 'aria' | 'native' = 'aria'
validationState?: ValidationState
width?: Responsive<DimensionValue>
zIndex?: Responsive<number>
}/@react-spectrum/autocomplete:SpectrumSearchAutocompleteProps SpectrumSearchAutocompleteProps <T> {
UNSAFE_className?: string
UNSAFE_style?: CSSProperties
align?: 'start' | 'end' = 'start'
alignSelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'center' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'stretch'>
aria-activedescendant?: string
aria-autocomplete?: 'none' | 'inline' | 'list' | 'both'
aria-controls?: string
aria-describedby?: string
aria-details?: string
aria-errormessage?: string
aria-haspopup?: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'
aria-label?: string
aria-labelledby?: string
autoComplete?: string
autoCorrect?: string
autoFocus?: boolean
bottom?: Responsive<DimensionValue>
children: CollectionChildren<T>
contextualHelp?: ReactNode
defaultInputValue?: string
defaultItems?: Iterable<T>
description?: ReactNode
direction?: 'bottom' | 'top' = 'bottom'
disabledKeys?: Iterable<Key>
end?: Responsive<DimensionValue>
enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'
errorMessage?: ReactNode | (ValidationResult) => ReactNode
excludeFromTabOrder?: boolean
flex?: Responsive<string | number | boolean>
flexBasis?: Responsive<number | string>
flexGrow?: Responsive<number>
flexShrink?: Responsive<number>
form?: string
gridArea?: Responsive<string>
gridColumn?: Responsive<string>
gridColumnEnd?: Responsive<string>
gridColumnStart?: Responsive<string>
gridRow?: Responsive<string>
gridRowEnd?: Responsive<string>
gridRowStart?: Responsive<string>
height?: Responsive<DimensionValue>
icon?: ReactElement | null
id?: string
inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
inputValue?: string
isDisabled?: boolean
isHidden?: Responsive<boolean>
isQuiet?: boolean
isReadOnly?: boolean
isRequired?: boolean
items?: Iterable<T>
justifySelf?: Responsive<'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'>
label?: ReactNode
labelAlign?: Alignment = 'start'
labelPosition?: LabelPosition = 'top'
left?: Responsive<DimensionValue>
loadingState?: LoadingState
margin?: Responsive<DimensionValue>
marginBottom?: Responsive<DimensionValue>
marginEnd?: Responsive<DimensionValue>
marginStart?: Responsive<DimensionValue>
marginTop?: Responsive<DimensionValue>
marginX?: Responsive<DimensionValue>
marginY?: Responsive<DimensionValue>
maxHeight?: Responsive<DimensionValue>
maxLength?: number
maxWidth?: Responsive<DimensionValue>
menuTrigger?: MenuTriggerAction = 'input'
menuWidth?: DimensionValue
minHeight?: Responsive<DimensionValue>
minLength?: number
minWidth?: Responsive<DimensionValue>
name?: string
necessityIndicator?: NecessityIndicator = 'icon'
onBeforeInput?: FormEventHandler<HTMLInputElement>
onBlur?: (FocusEvent<T>) => void
- onChange?: (T) => void
onClear?: () => void
onCompositionEnd?: CompositionEventHandler<HTMLInputElement>
onCompositionStart?: CompositionEventHandler<HTMLInputElement>
onCompositionUpdate?: CompositionEventHandler<HTMLInputElement>
onCut?: ClipboardEventHandler<HTMLInputElement>
onFocus?: (FocusEvent<T>) => void
onFocusChange?: (boolean) => void
onInput?: FormEventHandler<HTMLInputElement>
onInputChange?: (string) => void
onKeyDown?: (KeyboardEvent) => void
onKeyUp?: (KeyboardEvent) => void
onLoadMore?: () => void
onOpenChange?: (boolean, MenuTriggerAction) => void
onPaste?: ClipboardEventHandler<HTMLInputElement>
onSelect?: ReactEventHandler<HTMLInputElement>
onSubmit?: (string | null, Key | null) => void
order?: Responsive<number>
pattern?: string
position?: Responsive<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'>
right?: Responsive<DimensionValue>
shouldFlip?: boolean = true
spellCheck?: string
start?: Responsive<DimensionValue>
top?: Responsive<DimensionValue>
type?: 'text' | 'search' | 'url' | 'tel' | 'email' | 'password' | (string & {
}) = 'search'
validate?: (string) => ValidationError | boolean | null | undefined
validationBehavior?: 'aria' | 'native' = 'aria'
validationState?: ValidationState
width?: Responsive<DimensionValue>
zIndex?: Responsive<number>
}@react-stately/combobox/@react-stately/combobox:useComboBoxState-useComboBoxState <T extends {}> {
+useComboBoxState <M extends SelectionMode = 'single', T extends {}> {
- props: ComboBoxStateOptions<T>
+ props: ComboBoxStateOptions<T, M>
returnVal: undefined
}/@react-stately/combobox:ComboBoxStateOptions-ComboBoxStateOptions <T> {
+ComboBoxStateOptions <M extends SelectionMode = 'single', T> {
allowsCustomValue?: boolean
allowsEmptyCollection?: boolean
autoFocus?: boolean
collection?: Collection<Node<T>>
defaultFilter?: FilterFn
defaultInputValue?: string
defaultItems?: Iterable<T>
- defaultSelectedKey?: Key
+ defaultValue?: ValueType<SelectionMode>
description?: ReactNode
disabledKeys?: Iterable<Key>
errorMessage?: ReactNode | (ValidationResult) => ReactNode
inputValue?: string
isDisabled?: boolean
isInvalid?: boolean
isReadOnly?: boolean
isRequired?: boolean
items?: Iterable<T>
label?: ReactNode
menuTrigger?: MenuTriggerAction = 'input'
onBlur?: (FocusEvent<HTMLInputElement>) => void
+ onChange?: (T) => void
onFocus?: (FocusEvent<HTMLInputElement>) => void
onFocusChange?: (boolean) => void
onInputChange?: (string) => void
onKeyDown?: (KeyboardEvent) => void
onKeyUp?: (KeyboardEvent) => void
onOpenChange?: (boolean, MenuTriggerAction) => void
- onSelectionChange?: (Key | null) => void
placeholder?: string
- selectedKey?: Key | null
+ selectionMode?: SelectionMode = 'single'
shouldCloseOnBlur?: boolean
validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
validationBehavior?: 'aria' | 'native' = 'aria'
+ value?: ValueType<SelectionMode>
}/@react-stately/combobox:ComboBoxState-ComboBoxState <T> {
+ComboBoxState <M extends SelectionMode = 'single', T> {
close: () => void
collection: Collection<Node<T>>
commit: () => void
commitValidation: () => void
defaultInputValue: string
- defaultSelectedKey: Key | null
+ defaultValue: ValueType<SelectionMode>
disabledKeys: Set<Key>
displayValidation: ValidationResult
focusStrategy: FocusStrategy | null
inputValue: string
isFocused: boolean
isOpen: boolean
open: (FocusStrategy | null, MenuTriggerAction) => void
realtimeValidation: ValidationResult
resetValidation: () => void
revert: () => void
- selectedItem: Node<T> | null
- selectedKey: Key | null
+ selectedItems: Array<Node<T>>
selectionManager: SelectionManager
setFocused: (boolean) => void
setInputValue: (string) => void
setOpen: (boolean) => void
- setSelectedKey: (Key | null) => void
+ setValue: (Key | readonly Array<Key> | null) => void
toggle: (FocusStrategy | null, MenuTriggerAction) => void
updateValidation: (ValidationResult) => void
+ value: ValueType<SelectionMode>
} |
devongovett
commented
Feb 4, 2026
| }); | ||
|
|
||
| // Announce when a selection occurs for VoiceOver. Other screen readers typically do this automatically. | ||
| // TODO: do we need to do this for multi-select? |
Member
Author
There was a problem hiding this comment.
Right now I just hear "51 options available", which interrupts the default selection announcement from VO. But I also don't hear this with single select. We should discuss whether we can just remove the custom announcements entirely at this point.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2140
Similar to #8734, this adds support for the
selectionModeprop toComboBox, deprecatesselectedKey/onSelectionChange, and addsvalue/onChangewhich can be arrays in multi-select mode. When multi-select is enabled, the input value is cleared when an item is selected. It's the dev's responsibility to render the selected items somehow, e.g. using a TagGroup. Eventually we will have TagField which will improve this further to support tags that are "inline" with the input, but using an adjacent TagGroup is fine for now.To do:
ComboBoxStateContextto get the selected items but we could add a more first-class API for this.selectionMode/value/onChangefrom S2 props for now? Do we want to support this case or wait for TagField?