Raft 设计系统

组件 · 输入

ComboboxSelect

A ready-made searchable select driven by an array of options, for one or a few of many values, with groups, remote search and creation.

实验性
import { ComboboxSelect } from "@botiverse/raft-ui/wip";
本页尚未翻译
译文完成之前,这里显示英文原文。 阅读英文版

ComboboxSelect is a field at the control height (44px on a phone, 32px on a desktop) that people type into to find one or a few values in a long list, on a phone as on a desktop. It is for filtering, searching a server and creating values; a field people only pick from is a Select. It takes an array of options, flat or grouped, and builds the field, the list, the chips and the empty and loading lines from it. It is exported from @botiverse/raft-ui/wip, and its API can change in any release.

combobox-select/compositions/preview

When to use

The full choice between pickers is in Choosing a component.

SituationUse
One of many values, with searchComboboxSelect
A few of many values (people, agents, labels)ComboboxSelect with multiple
Values that come from a server as people typeComboboxSelect with remote
One of up to 10 values, or any value picked without typingSelect
A filter over a list, or a layout the options array can't expressCombobox

Variants

VariantUse for
Single (default)One value. The list shows a check mark on the current value
multipleA few values. Each chosen value becomes a chip in the field
Grouped optionsValues that belong together, such as models by provider. Group labels head each group
remote with onSearchResults the caller fetches. The component shows them without filtering
createValues people can add by typing a new one
combobox-select/compositions/combobox-select
combobox-select/compositions/model-select
combobox-select/compositions/async-search

States

  • Loading: loading shows a loading line in the list. Keep the list open while a choice is being saved, and show it with loading.
  • Not loaded yet: fallbackOption gives a label to a chosen value whose option hasn't arrived, so the field stays readable.
  • Invalid: status="error" or aria-invalid gives the field a danger-strong border.
  • No match: the list says so in one line. Don't leave an empty popup.

Behavior

  • Filter the options as people type, unless remote is set.
  • With multiple, keep the list open when a value is chosen; clicking outside or Esc closes it. Typing continues after the last chip.
  • An option is as tall as its content: one line is 36px, and a second line adds only its own height, with the same space above and below the text (Forms).
  • A long option wraps to two lines, then truncates; a long chip truncates at the width of the field. Each has its whole text as its title while it is cut off (Forms).
  • A chip, the clear button and the toggle sit 6px inside the field's outer edge and 6px apart: 20px on a desktop and 32px on a phone, so the field keeps its height with chips in it. They wear radius-chip, like every control embedded in a frame; a chip's remove button wears radius-sticker 2px inside the chip, with its focus ring drawn inside it (Shape).
  • With maxTagCount, chips beyond the count collapse into a count while the field is not focused. All of them come back on focus.
  • The list grows to 16rem (256px), its 4px padding above and below included: six 36px rows and part of a seventh, which shows there are more. Then it scrolls. listMaxHeight (a CSS length, or pixels, measured the same way) moves that cap, and listMaxHeight="16rem" is the same as leaving it out. Raise it where the field has a tall space of its own below it, such as a tall bottom Drawer; lower it when the list would cover something people need to see while they choose.

On a phone

A phone here is a touch screen narrower than the desktop layout, (width < 48rem) and (pointer: coarse). ComboboxSelect is the same text field there as on a desktop, because typing is what it is for: a tap focuses it and brings up the keyboard, typing filters the list, and create and remote work in the field. There is no bottom sheet. When people only pick a value and never need to type, use a Select instead: it opens without the keyboard.

  • The list always opens below the field, between the field and the keyboard, as a native autocomplete does, and never flips. Which side has more room depends on whether the keyboard happens to be up, so a list that chose the roomier side opened downward one time and upward the next on the same field. When fewer than three rows fit below, the field scrolls up to the top of what is visible instead. As the keyboard comes up or goes, the address bar moves, or the filtered list shrinks and grows, only the list's height changes: the field stays where it is.
  • The input stays at 16px or more at every size, so iOS doesn't zoom into it.
  • size="lg" is a 52px field with 40px chips and buttons, 6px inside it; size="sm" is a 28px field with 22px chips and buttons, 3px inside it, as close as a segmented control's items sit to their track.
  • Inside a bottom Drawer, give the drawer a height that doesn't follow the keyboard (lvh, not dvh) and room below the field for the list, and raise listMaxHeight when the drawer is tall.
combobox-select/compositions/in-drawer
combobox-select/compositions/many-options

This is for finding a value in a list. Combobox, the free-text field for mentions and tags, will get its own phone pattern: the input pinned at the top of a sheet or a full screen, with the suggestions between it and the keyboard.

Content

  • Write the placeholder as what to do: Choose a model.
  • Write the empty line as what found nothing: No models match "zz".

Accessibility

ComboboxSelect wraps Base UI Combobox: a text input with role="combobox" that controls a listbox.

  • Name the field with a FieldLabel (Field) whose htmlFor matches its id, or with aria-label.
  • Left Arrow at the start of the input moves focus to the chips, and Backspace or Delete removes the focused chip. Each remove button is named after its value (Remove Bugen).
  • On a phone the field is the same text input, named the same way.
  • Translate these announcements with messages when the interface is not in English.

API

ComboboxSelect

实验性
Prop类型默认值
clearablebooleanfalse
clearFilterAfterSelectbooleantrue
creatableAlias for create, useful when matching Select libraries that call this mode creatable.boolean
createbooleanfalse
defaultValueNonNullable<ComboboxSelectSelection<Multiple>>
disabledFieldkeyof Option"disabled" as keyof Option
emptyContentReactNode"No options found"
fallbackOptionSupplies labels for values that have not been loaded yet.(value: ComboboxSelectValue) => Option
filterfalse | ((query: string, option: Option) => boolean)
filterablebooleantrue
inputPropsOmit<AutocompleteInputProps, "defaultValue" | "value" | "children" | "size">
labelFieldkeyof Option"label" as keyof Option
listMaxHeightThe tallest the list gets before it scrolls, its 6px padding above and below included: a CSS length, or a number of pixels. On a phone the list also gets shorter to fit the room on its side, and opens above when that side has more room and below is short of this height.string | number"16rem"
loadingbooleanfalse
loadingContentReactNode"Loading…"
maxTagCountCollapse chips when the control is unfocused; all remain reachable when focused.number
messagesPartial<ComboboxSelectMessages>
multipleNonNullable<Multiple>
onClear() => void
onCreateCalled only when the creation row is selected. Return null to reject creation.(query: string) => Option | null
onSearch(query: string) => void
onValueChange(value: ComboboxSelectSelection<Multiple>, options: Multiple extends true ? (Option | undefined)[] : Option | null | undefined, details: BaseCombobox.Root.ChangeEventDetails) => void
options (required)readonly (Option | ComboboxSelectGroup<Option>)[]
placeholderstring"Select an option"
popupPropsOmit<AutocompletePopupProps, "children">
portalPropsAutocompletePortalProps
positionerPropsOmit<AutocompletePositionerProps, "children">
remoteDisplay the provided results without local filtering. Requests belong to the caller.booleanfalse
renderCreateLabel(query: string) => ReactNodedefaultRenderCreateLabel
renderLabel(option: Option) => ReactNode
renderOptionCustomizes option content while preserving the Base UI option element.(option: Option, info: { selected: boolean; }) => ReactNode
renderTagCustomizes chip content while preserving keyboard navigation and removal.(option: Option) => ReactNode
showArrowbooleantrue
size"sm" | "md" | "lg""md"
status"warning" | "error"
valueNonNullable<ComboboxSelectSelection<Multiple>>
valueFieldkeyof Option"value" as keyof Option
autoCompleteProvides a hint to the browser for autofill.string
autoHighlightWhether the first matching item is highlighted automatically while filtering.booleantrue
defaultInputValueThe uncontrolled input value when initially rendered.string | number | readonly string[]
defaultOpenWhether the popup is initially open.booleanfalse
disabledWhether the component should ignore user interaction.booleanfalse
formIdentifies the form that owns the internal input. Useful when the combobox is rendered outside the form.string
inputValueThe input value of the combobox. Use when controlled.string | number | readonly string[]
nameIdentifies the field when a form is submitted.string
onInputValueChangeEvent handler called when the input value changes.(inputValue: string, eventDetails: BaseCombobox.Root.ChangeEventDetails) => void
onOpenChangeEvent handler called when the popup is opened or closed.(open: boolean, eventDetails: BaseCombobox.Root.ChangeEventDetails) => void
onOpenChangeCompleteEvent handler called after any animations complete when the popup is opened or closed.(open: boolean) => void
openWhether the popup is currently open. Use when controlled.boolean
readOnlyWhether the user should be unable to choose a different option from the popup.booleanfalse
requiredWhether the user must choose a value before submitting a form.booleanfalse

Also takes the attributes of its element.

改动

  • 0.12.0 · Breaking ChangesComboboxSelect always takes typing on a phone, as on a desktop: tapping the field focuses it and typing filters; create and remote search work in the field. The phone-only pick-only mode and bottom sheet from 0.10.5 are gone, with their props sheetThreshold, sheetTitle and messages.search / done / close. A pick-only field uses Select. The list still picks its side once and doesn't flip while the keyboard opens.
  • 0.12.0 · AddedComboboxSelect takes listMaxHeight (a CSS length or pixels, default 16rem, the whole list's height); the side the list opens on follows it.
  • 0.10.5 · AddedComboboxSelect takes sheetThreshold (default 10) and sheetTitle, and its messages take search, done and close for the phone's bottom sheet.
  • 0.10.5 · FixedComboboxSelect on a phone (a touch screen under 48rem) no longer focuses a text input and raises the keyboard when people only pick a value. Up to sheetThreshold options open as an anchored list that picks its side once and doesn't flip while the keyboard or the address bar moves; more options, create and remote search open a bottom sheet with the search input at its top. Desktop and touch tablets are unchanged.
  • 0.10.0 · Breaking ChangesField frames (Input, Textarea, InputGroup, Select, Combobox, ComboboxSelect, Composer, option Card, SearchTrigger) are drawn at 1px in line-field instead of 1.5px, on the phone too.
  • 0.10.0 · ChangedTooltip, ComboboxSelect options, Steps and Timeline use the type scale instead of fixed sizes, so they follow the desktop values.