Skip to main content

ComboBox

Button showing the current choice, with a list of options under it. The choice itself stays in your state: the component reports a click and nothing more.

ComboBoxComboBox

Use this when / not when​

  • Use to pick one value from a short, known list: a role, a time zone, a filter, a language.
  • Not for a value the user types or searches. There is no search field here — withSearch is declared and does nothing — so a long list wants Selector or an input of your own.
  • Not for a menu of actions. A menu is DropDown with DropDownItems; this one is about a value.
  • Not on its own in a form: a labelled field is FieldContainer around it.
  • Not for many values at once. It shows one option and a +N badge you maintain yourself.

Import​

import {
ComboBox,
ComboBoxSize,
} from "@onlyoffice/apps-ui-kit/components/combobox";

Also exported from the root barrel @onlyoffice/apps-ui-kit.

Needs ThemeProvider from @onlyoffice/apps-ui-kit/providers/theme for the button and list colours, and TranslationProvider from @onlyoffice/apps-ui-kit/providers/translation because the options are rendered as DropDownItems, which read a translated label for their paid badge.

Stories​

Default​

A fixed-width combo box with a placeholder in the button, as a form shows it before anything is chosen. Pick an option to see it replace the placeholder, and change any other prop live in the Controls panel below.

DefaultDefault

Different Sizes​

The fixed widths side by side, to pick the one that fits the longest label a form expects: base 173px, middle 300px, big 350px, huge 500px, and content, as wide as the label (size, with scaled off).

Different SizesDifferent Sizes

With Icons​

Options with an icon each, so an action is recognised before its label is read; pick one and its icon moves into the button next to the label (icon on each option).

With IconsWith Icons

With Option Descriptions​

Options whose label alone does not explain the choice: open the list and each row shows the label with a line of explanation under it (description on each option). The selected row is highlighted and stays clickable (displaySelectedOption).

With Option DescriptionsWith Option Descriptions

Disabled​

A choice that cannot be changed right now, kept on screen so the reader still sees the value: the button is greyed out and a click does not open the list (isDisabled).

DisabledDisabled

With Selected Option​

A form that opens with a value already chosen. Open the list: the chosen option is greyed out and cannot be picked again, because displaySelectedOption is off; turn it on to keep it clickable and highlighted instead.

With Selected OptionWith Selected Option

Custom Styling​

Colour-coded values, such as priorities, where the colour says more than the word: the button draws the chosen option as a badge in its own text and background colours (type="badge", with color and backgroundColor on each option). Pick another priority to see the badge change; the rows of the list stay plain.

Custom StylingCustom Styling

Loading State​

A value that is still being fetched or saved: the label and the arrow give way to a spinner and a click does not open the list until loading ends (isLoading).

Loading StateLoading State

Right To Left​

The combo box under a right-to-left interface: the icon and the label start at the right edge, the icon is mirrored, and the arrow sits at the left end. Open the list to see its rows aligned to the right as well. The direction comes from the theme's interfaceDirection (the Direction toolbar); the wrapper also carries dir="rtl" for the rules that read the DOM direction.

Right To LeftRight To Left

Css Customization​

The border colours, the radius and the padding set on one wrapper -- the variables are listed under CSS variables on this page. Hover the button to see the hover colour and click it to see the open one.

Css CustomizationCss Customization

Minimal example​

selectedOption and options are both required, and onSelect is what updates the first.

import { useState } from "react";
import {
ComboBox,
type TOption,
} from "@onlyoffice/apps-ui-kit/components/combobox";

const ROLES: TOption[] = [
{ key: "viewer", label: "Viewer" },
{ key: "editor", label: "Editor" },
{ key: "owner", label: "Owner" },
];

export function RolePicker() {
const [role, setRole] = useState<TOption>(ROLES[0]);

return (
<div style={{ width: 240 }}>
<ComboBox
options={ROLES}
selectedOption={role}
onSelect={setRole}
scaled
scaledOptions
/>
</div>
);
}

Props​

PropertyTypeDescription
optionsTOption[]Required. The options. Each needs a unique key, and a label unless it is a separator; disabled, icon, description, isBeta and tooltip are passed to the row.
selectedOptionTOptionRequired. The option to show in the button. The component does not choose it: keep it in your own state and set it from onSelect.
advancedOptionsReactElement<{ children?: React.ReactNode; }, string | JSXElementConstructor<any>>Element whose children replace the list entirely, for a menu that is not a list of options. options is then used only for the button.
advancedOptionsCountnumberHow many advanced options there are, for deciding the mobile layout.
childrenReactNodeContent rendered inside the button, before the label. A click on it is ignored unless disableIconClick is false.
classNamestringApplied to the element that wraps the button and the list.
comboIconReactNodeIcon to draw instead of the arrow: a component, an element, or a URL.
dataTestIdstringValue of data-testid on the wrapper. Default: "combobox".
directionXTDirectionXPreferred horizontal side of the list; it flips to fit the viewport.
directionYTDirectionYPreferred vertical side of the list; it flips to fit the viewport.
disableIconClickbooleanWhether a click on children is swallowed instead of opening the list. Default: true.
disableItemClickbooleanStops the list opening at all, without the disabled styling.
disableItemClickFirstLevelbooleanThe same, but only for first-level items on a touch device.
displayArrowbooleanDraws the arrow even when there are no options to open.
displaySelectedOptionbooleanKeeps the option that is currently selected usable and highlights it. Without it that option is rendered disabled, so the value cannot be picked again.
displayTypeComboBoxDisplayTypetoggle renders the button alone and no list at all, for a control that only looks like a combo box. Default: ComboBoxDisplayType.default.
fillIconbooleanRecolours the selected option's icon to the text colour.
fixedDirectionbooleanKeeps directionX and directionY as given instead of flipping them.
forceCloseClickOutsidebooleanStops the outside-click listener being registered.
hideMobileViewbooleanKeeps the list anchored to the button on a phone instead of turning it into a bottom sheet.
idstringApplied to the element that wraps the button and the list.
isAsidebooleanMarks the list's backdrop as belonging to a side panel.
isDefaultModebooleanWhether the list is rendered in a portal on document.body. Default: true.
isDisabledbooleanGreys the button out and stops it opening.
isLoadingbooleanReplaces the arrow with a spinner and stops the button opening.
isMobileViewbooleanPins the list to the bottom of the screen, full width, in portrait.
isNoFixedHeightOptionsbooleanRenders the options in a plain scrollbar instead of the virtualised list.
manualWidthstringWidth of the list as a CSS length. It has nothing to do with the button's width — use scaledOptions for that. Default: "200px".
manualXstring(Non-portal mode) Exact horizontal offset of the list from the button.
manualYnumber | string(Non-portal mode) Exact vertical offset of the list from the button.
modernViewbooleanCompact button with no background until it is open.
noBorderbooleanRemoves the button's border.
noSelectbooleanWhether the button's text cannot be selected. Default: true.
offsetXnumberHorizontal offset of the list, in pixels.
onBackdropClick(e: Event) => voidCalled when a click outside closes the list, if withBackdrop is on.
onClickSelectedItem(option: TOption) => voidCalled when the option already selected is clicked again.
onSelect(option: TOption) => voidCalled with the option that was clicked. Nothing changes on its own — selectedOption is yours to update.
onToggle(e: React.MouseEvent<HTMLDivElement>, isOpen: boolean) => voidCalled when the button is clicked, with the state being asked for. Passing it without onBackdropClick also stops a click outside closing the list.
openedbooleanOpens or closes the list from outside. It seeds the internal state rather than controlling it: the next click on the button wins until this changes.
optionStyleCSSPropertiesInline style applied to every option.
plusBadgeValuenumberNumber shown as +N after the label, for a multi-select summary.
rolestringIgnored. Nothing reads this prop.
scaledbooleanMakes the button take the full width of its parent, which overrides size. Default: true.
scaledOptionsbooleanMatches the list's width to the button's instead of manualWidth.
searchPlaceholderstringIgnored. Nothing reads this prop; there is no search field.
setIsOpenItemAccess(isOpen: boolean) => voidCalled with the open state whenever it changes, alongside onToggle.
shouldShowBackdropbooleanRenders the backdrop even when another one is already on screen.
showDisabledItemsbooleanIgnored. The list is always told to keep disabled options.
size"base" | "big" | "content" | "huge" | "middle"One of the fixed widths — 173, 300, 350 or 500px, or the content's own. It only applies when scaled is false. Default: ComboBoxSize.base.
styleCSSPropertiesApplied to the wrapper and, again, to the list.
tabIndexnumberPosition of the button in the tab order. It is 0 by default, so the control is reachable; pass -1 to take it off the tab order. Default: 0.
textOverflowbooleanTruncates an option's label with an ellipsis instead of wrapping it.
titlestringHover tooltip for the whole control. It needs RootTooltip mounted.
topSpacenumberSpace to leave above the list when it opens upwards, in pixels.
typeTComboboxShape of the button: badge draws the label as a coloured badge, onlyIcon drops the label, descriptive adds the option's description under it.
useImageIconbooleanDraws the kit's placeholder image next to the selected option's icon.
usePortalBackdropbooleanMoves the list's backdrop into the portal, above the page.
withBackdropbooleanWhether the list renders a backdrop to catch the next click. Default: true.
withBackgroundbooleanGives that backdrop its dimming background.
withBlurbooleanIgnored. It reaches the list, which does not read it either.
withLabelbooleanWhether the selected option is matched by label rather than by key. Default: true.
withoutArrowbooleanHides the arrow, whatever displayArrow and the options say.
withoutBackgroundbooleanMakes that backdrop transparent.
withoutPaddingbooleanRemoves the vertical padding around the button.
withSearchbooleanIgnored. Nothing reads this prop; there is no search field.

Enums​

EnumMembers
ComboBoxDisplayTypedefault, toggle

Recipes​

Open and close, controlled​

opened seeds the open state rather than owning it, and onToggle reports every click on the button. Pass onBackdropClick as well, or a click outside stops closing the list.

import { useState } from "react";
import {
ComboBox,
type TOption,
} from "@onlyoffice/apps-ui-kit/components/combobox";

const SORTS: TOption[] = [
{ key: "name", label: "Name" },
{ key: "date", label: "Last modified" },
];

export function SortPicker() {
const [sort, setSort] = useState<TOption>(SORTS[0]);
const [open, setOpen] = useState(false);

return (
<ComboBox
options={SORTS}
selectedOption={sort}
opened={open}
onToggle={(_event, next) => setOpen(next)}
onBackdropClick={() => setOpen(false)}
onSelect={(option) => {
setSort(option);
setOpen(false);
}}
/>
);
}

Loading and disabled​

import {
ComboBox,
type TOption,
} from "@onlyoffice/apps-ui-kit/components/combobox";

const PLACEHOLDER: TOption = { key: "none", label: "Loading…" };

export function RoomPicker({
rooms,
isLoading,
}: {
rooms: TOption[];
isLoading: boolean;
}) {
return (
<ComboBox
options={rooms}
selectedOption={rooms[0] ?? PLACEHOLDER}
isLoading={isLoading}
isDisabled={rooms.length === 0}
onSelect={() => {}}
/>
);
}

A long list that scrolls​

dropDownMaxHeight is what gives the list a scrollbar; without it every option is rendered.

import { useState } from "react";
import {
ComboBox,
type TOption,
} from "@onlyoffice/apps-ui-kit/components/combobox";

export function TimezonePicker({ zones }: { zones: TOption[] }) {
const [zone, setZone] = useState<TOption>(zones[0]);

return (
<ComboBox
options={zones}
selectedOption={zone}
onSelect={setZone}
dropDownMaxHeight={320}
displaySelectedOption
textOverflow
/>
);
}

Behaviour the types don't state​

  • The selected option is matched by its label, not its key. withLabel is true by default, and the comparison is option.label === selectedOption.label, so two options that read the same are both treated as selected. Pass withLabel={false} to compare keys instead.
  • The option you are on is rendered disabled. Unless displaySelectedOption is set, the option matching the selection is greyed out and cannot be clicked, which is how the kit shows the current value — and why re-picking it does nothing.
  • Nothing selects anything. onSelect hands you the option; selectedOption is yours to update, and the component mirrors whatever you pass back.
  • The list is 200px wide whatever the button is. manualWidth defaults to "200px"; scaledOptions is what matches it to the button, and even then the width is measured from the button on a later render, so the first open can still be 200px.
  • scaled is on by default, so the control fills its parent and size has no effect. Turn it off to get the 173px of ComboBoxSize.base.
  • The keyboard does not work. The component listens for ArrowDown and Enter on the document and looks for options by the test id drop-down-item — which its own options never carry, because it gives each one a test id of its own. The result is that the arrows and Enter do nothing while the list is open, and Enter is swallowed for the rest of the page as well.
  • Loading hides the choice. isLoading draws a spinner in the button and hides its label, icon, children and arrow while keeping their space, so the button keeps its width but no longer shows the current value.
  • A list of four or more options becomes a bottom sheet on a phone. hideMobileView keeps it anchored to the button.
  • An option's icon has to be a component or a URL: an element is ignored by the button, and a URL is fetched at runtime by react-svg.
  • searchPlaceholder, withSearch, showDisabledItems, withBlur and role are declared and never read. Disabled options are always kept in the list.
  • The component is memoised with a deep comparison of all its props, so a new options array on every render costs a full walk of it rather than a re-render.

Sub-components​

ComboButton is the button alone, exported for a control that needs the same shape without a list of its own. It takes the selectedOption, size, type, isOpen and isLoading props described above and calls onClick; everything else — opening, choosing, closing — is yours.

CSS variables​

VariableDefaultEffect
--combobox-border-colortheme greyBorder colour of the button
--combobox-hover-border-colortheme greyBorder colour of the button under the pointer
--combobox-focus-border-colorthe accent colour (white in dark theme)Border colour of the button while its list is open, hovered or not
--combobox-radius3pxCorner radius of the button when it has a border
--combobox-inner-padding4px 0Space above and below the button, inside the wrapper
--combobox-base-width173pxWidth of the wrapper at ComboBoxSize.base
--combobox-middle-width300pxWidth of the wrapper at ComboBoxSize.middle
--combobox-big-width350pxWidth of the wrapper at ComboBoxSize.big
--combobox-huge-width500pxWidth of the wrapper at ComboBoxSize.huge

The four widths only apply with scaled off, and they size the wrapper, not the button: the button keeps its own fixed 173, 300, 350 or 500px, so a larger value widens only the empty area beside it. Under noBorder the button has no border and so no radius.

--combobox-bg and --combobox-open-bg are read by the stylesheet and paint nothing: the rule that uses them waits for a noBorder class on the wrapper, which the component never sets.

The list is a DropDown and takes that component's variables.

Accessibility​

  • The button is a <div role="button"> with aria-haspopup="listbox", and aria-expanded and aria-pressed that are both true while the list is open; aria-disabled follows isDisabled. Its tabIndex defaults to 0, so it is reachable. It used to default to -1, which left the control off the keyboard entirely. Focus gets there, but only a click opens the list: Enter and Space on the button do nothing, Escape does not close it, and the arrow keys do not move through it, as above.
  • The options are role="option" rows in a role="listbox" that is not linked to the button by aria-controls or aria-activedescendant.
  • The control's name comes from its content, the selected option's label, which says the value but not what is being chosen. Put it in a FieldContainer, or give it an aria-label through a wrapper.
  • title renders the kit's hover tooltip, which is not an accessible name and needs RootTooltip mounted.

Test ids​

Elementdata-testid
The controlcombobox, overridable with dataTestId
The listset by dropDownTestId, otherwise dropdown
An optionthe option's dataTestId, otherwise drop_down_item_<key>

The button inside carries data-test-id="combo-button" — with hyphens, unlike everything else in the kit — and so do its icon, badge and arrow.