Package
@carbon/react
Browser
Chrome
Package version
v1.112.0
React version
v18.3.1
Description
FilterableMultiSelect id handling issue
FilterableMultiSelect derives its internal DOM ids and its outside-click detection from the caller-supplied id prop instead of from a unique per-instance id.
As a result, when two FilterableMultiSelect instances are rendered with the same id, the second instance becomes unusable—opening its menu and clicking an item immediately closes the menu (focus is lost, no value can be selected).
Current use of useId
This is notable because FilterableMultiSelect creates a unique instance id with useId(), but only uses it for the helper text:
import { useId } from '../../internal/useId';
// ...
const filterableMultiSelectInstanceId = useId();
// ...
const helperId = !hasHelper
? undefined
: `filterablemultiselect-helper-text-${filterableMultiSelectInstanceId}`;
However, the ids that actually need to be unique are derived from the caller id:
const labelId = `${id}-label`;
const menuId = `${id}__menu`;
const inputId = `${id}-input`;
useCombobox({
/* ... */
id,
labelId,
menuId,
inputId,
/* ... */
});
Outside-click handler (root cause)
The functional break is caused by the outside-click handler, which looks the component up by id:
const handleClickOutside = (event: MouseEvent) => {
const target = event.target;
if (!(target instanceof HTMLElement)) {
return;
}
const wrapper = document
.getElementById(id) // ← returns the FIRST element with this id
?.closest(`.${prefix}--multi-select__wrapper`);
if (wrapper && !wrapper.contains(target)) { // ← second instance's clicks look "outside"
if (isOpen || inputFocused) {
setIsOpen(false); // ← force-closes the menu
setInputFocused(false);
setInputValue('');
}
}
};
When two instances share id, document.getElementById(id) always resolves to the first instance's ListBox.
So for the second instance:
wrapper is the first instance's wrapper.
- A click on the second instance is not contained by the first instance's wrapper.
- Therefore
!wrapper.contains(target) is true, and the handler runs, closing the menu before the selection registers.
Accessibility defect
Additionally:
- The duplicated
menuId (${id}__menu) and
- The input’s
aria-controls={menuId}
mean both instances point their aria-controls at the same element id, which is an a11y defect independent of the click handler.
Comparison with ComboBox/Dropdown
ComboBox and Dropdown do not exhibit this issue:
- They do not use
getElementById(id)-based outside-click detection.
- As a result, multiple instances with the same
id remain independently operable.
Reproduction/example
https://jvmiqgrynwgithub-lf0a--5173--017acfb7.local-credentialless.webcontainer.io/
Steps to reproduce
- Render the two FilterableMultiSelects above (same id="field-1").
- Open the first one and select an item → works.
- Open the second one and click an item.
Suggested Severity
High (Severity 2) = User cannot complete task, and/or no workaround within the user experience of a given component.
Project name
N/A
Code of Conduct
Package
@carbon/react
Browser
Chrome
Package version
v1.112.0
React version
v18.3.1
Description
FilterableMultiSelect id handling issue
FilterableMultiSelectderives its internal DOM ids and its outside-click detection from the caller-suppliedidprop instead of from a unique per-instance id.As a result, when two
FilterableMultiSelectinstances are rendered with the sameid, the second instance becomes unusable—opening its menu and clicking an item immediately closes the menu (focus is lost, no value can be selected).Current use of
useIdThis is notable because
FilterableMultiSelectcreates a unique instance id withuseId(), but only uses it for the helper text:However, the ids that actually need to be unique are derived from the caller
id:Outside-click handler (root cause)
The functional break is caused by the outside-click handler, which looks the component up by
id:When two instances share
id,document.getElementById(id)always resolves to the first instance's ListBox.So for the second instance:
wrapperis the first instance's wrapper.!wrapper.contains(target)istrue, and the handler runs, closing the menu before the selection registers.Accessibility defect
Additionally:
menuId(${id}__menu) andaria-controls={menuId}mean both instances point their
aria-controlsat the same element id, which is an a11y defect independent of the click handler.Comparison with ComboBox/Dropdown
ComboBoxandDropdowndo not exhibit this issue:getElementById(id)-based outside-click detection.idremain independently operable.Reproduction/example
https://jvmiqgrynwgithub-lf0a--5173--017acfb7.local-credentialless.webcontainer.io/
Steps to reproduce
Suggested Severity
High (Severity 2) = User cannot complete task, and/or no workaround within the user experience of a given component.
Project name
N/A
Code of Conduct