Skip to main content

rc-select

Select-only combobox backed by a native <select>, following the WAI-ARIA Combobox pattern.

Package
@rcarls/rc-select
Element
<rc-select>
Native dependency
Requires a direct child native select
State model
Controlled or uncontrolled selection
Main events
rc-select-openrc-select-closerc-select-change

Installation

npm install @rcarls/rc-select
import '@rcarls/rc-select/define';

Live demo

Theming

The default demo mode shows the component without a package theme. Use the shared preview controls on this page to compare inherited, light, and dark color schemes or to apply the optional Material theme only inside the demo frames.

Known Issues

Firefox for Android: <label>-wrapped multiple selects (countermeasure implemented)

"Geckoview and Fennec open select dropdown on tap of parent label; other browsers do not." Bugzilla 1475723

To workaround this bug, rc-select force-disables the slotted <select multiple> and hooks into the associated form's submit and formdata events to preserve constraint validation and form data participation.

Label activation on a disabled control is a spec-defined no-op, mitigating the issue while maintaining progressive enhancement with minimal lightdom meddling.

However, there are two important caveats to this workaround:

  1. No native validation bubble. The browser cannot render its validation UI on a hidden control, so when submit-time enforcement finds the select invalid (e.g. required with nothing selected, or setCustomValidity()), the component cancels the submission, fires a cancelable invalid event on the select, and moves focus to the trigger. Call preventDefault() on the invalid event to supply your own error UI instead. novalidate forms, formnovalidate submitters, and form.submit() bypass enforcement, matching native scoping.
  2. Disabling through the select is one-way after upgrade. Enabling by removing the select's disabled (attribute or property) is captured: the component mirrors the intent and keeps the guard in place. But disabling through the select is not captured — the guard already holds the disabled attribute present, so re-setting it is indistinguishable from the guard's own writes. Set disabled on <rc-select> itself to disable the control after upgrade.

API

Properties

PropertyMarkupTypeDefaultDescription
openopenbooleanfalseReflects whether the popup listbox is open.
multiplemultiplebooleanfalseEnables selection of multiple options simultaneously.
disableddisabledbooleanfalseDisables the trigger and prevents the popup from opening.
placeholderplaceholderstring''Text shown in the trigger when no value is selected.
displaydisplay'auto' | 'chips' | 'compact''auto'Controls how selected values appear in the trigger. - `'chips'` — each selected value renders as a removable chip. - `'compact'` — selected values are summarized as "First, +N more". - `'auto'` — uses `'chips'` on pointer devices and `'compact'` on touch.
valueJS property onlyRCSelectValueNot specifiedCurrent selection. Host writes update selection silently; user interaction emits `rc-select-change`.
defaultValueJS property onlyRCSelectValue | undefinedNot specifiedInitial uncontrolled selection, applied before user or native state owns the value.
optionsJS property onlyListboxOption[] | undefinedNot specifiedProgrammatic option source. Omit to derive options from the slotted `<select>`.
selectedValuesJS property onlystring[]Not specifiedSelected values as a consistently-array-shaped convenience getter.

Methods

MethodDescription
openPopup()Opens the popup listbox if not already open or disabled.
closePopup(returnFocus: unknown)Closes the popup listbox.

Events

EventDetail typeDescription
rc-select-openCustomEventWhen the popup opens.
rc-select-closeCustomEventWhen the popup closes.
rc-select-changeCustomEventWhen user interaction changes selection. `detail: { value, selectedValues, selectedOptions }`

Slots

NameDescription
(default)Required. A native `<select>` element used for form submission and as the source of truth for options, multiple, and disabled state.
displayOptional. Replaces the default value label in the trigger.
toggle-indicatorOptional. Replaces the default chevron indicator. Accepts any element(s); the container shifts to inline-start in RTL via flex direction.

CSS Custom Properties

PropertyDefaultDescription
--rc-select-max-height20emMaximum popup height.
--rc-select-control-block-sizevar(--rc-control-block-size)Trigger block size.
--rc-select-padding-blockvar(--rc-control-padding-block)Trigger block-axis padding.
--rc-select-padding-inlinevar(--rc-control-padding-inline)Trigger inline-axis padding.
--rc-select-gapvar(--rc-control-gap)Gap between trigger content, chips, and icon.
--rc-select-radiusvar(--rc-control-radius)Trigger border radius.
--rc-select-bordervar(--rc-border)Trigger border.
--rc-select-listbox-radiusvar(--rc-control-radius)Popup listbox border radius.
--rc-select-listbox-bordervar(--rc-border)Popup listbox border.
--rc-select-shadowvar(--rc-shadow)Popup listbox box shadow.
--rc-select-listbox-padding-blockvar(--rc-control-padding-block)Popup listbox block padding.
--rc-select-chip-radiusvar(--rc-radius-md)Multi-select chip border radius.
--rc-select-chip-padding-block0.1emMulti-select chip block-axis padding.
--rc-select-chip-padding-inline0.3emMulti-select chip inline-axis padding.
--rc-select-chip-gapcalc(var(--rc-control-gap, 0.25em) * 0.8)Gap between chip label and remove icon.
--rc-select-chip-bordervar(--rc-border)Multi-select chip border.
--rc-select-toggle-indicator-size1.1emInline size of the toggle indicator container.

CSS Parts

PartDescription
anchorThe flex container wrapping chips and trigger.
triggerThe combobox trigger element (div).
chipsThe chips group container (when multiple).
chipIndividual chip (when multiple).
chip-labelText label inside a chip.
chip-removeRemove button inside a chip.
value-displayThe text label showing selected value(s).
toggle-indicatorThe open/close indicator container.
listboxThe `<rc-listbox>` popup element.