Back to Tooljet

Combobox — Rocket Design Spec

frontend/src/components/ui/Rocket/Combobox/Combobox.spec.md

3.20.154-lts9.2 KB
Original Source

Combobox — Rocket Design Spec

<!-- synced: 2026-03-19 -->

Overview

Combobox is a searchable dropdown for choosing one value from a filtered list. The trigger is an input field that filters options as the user types. Reuses the same visual tokens as Input/Select (bg, border, focus ring, sizing).

Wraps shadcn Combobox (@base-ui/react Combobox primitive).

v1 scope: Single-select only. Multi-select (chips) deferred to v2.

Sub-components

ComponentWrapsToken overrides
ComboboxInputshadcn ComboboxInputSame tokens as Select trigger — bg, border, focus, hover, disabled, error, sizes
ComboboxContentshadcn ComboboxContentbg-surface-layer-01, border-weak, rounded-lg (10px), elevation-300
ComboboxItemshadcn ComboboxItem32px height, text-default, hover → interactive-hover, rounded-md
ComboboxEmptyplain div (peer-based)text-placeholder, centered, hides when list has items
ComboboxListshadcn ComboboxListAdds tw-peer for empty state detection
ComboboxTriggershadcn ComboboxTriggerIcon colour icon-default
ComboboxLabelshadcn ComboboxLabeltext-placeholder, text-xs, font-semibold
ComboboxSeparatorshadcn ComboboxSeparatorbg-border-weak
Combobox (root)shadcn ComboboxProvides ComboboxAnchorContext for positioning

Re-exported from shadcn (no token overrides)

ComboboxValue, ComboboxGroup, ComboboxCollection

Props (ComboboxInput)

PropTypeValuesDefault
sizestringlarge | default | smalldefault
classNamestring
disabledbooleanfalse
readOnlybooleanfalse
loadingbooleanfalse
showTriggerbooleantrue
showClearbooleanfalse

Sizes (trigger/input)

ValueHeightFont sizeTailwind
large40px14px / 20pxtw-h-10 tw-text-lg
default32px12px / 18pxtw-h-8 tw-text-base
small28px12px / 18pxtw-h-7 tw-text-base

Token Mapping — Input/Trigger

ElementStateToolJet tokenTailwind class
backgrounddefault--background-surface-layer-01tw-bg-background-surface-layer-01
borderdefault--border-defaulttw-border-border-default
borderhover--border-stronghover:tw-border-border-strong
textdefault--text-defaulttw-text-text-default
placeholderdefault--text-placeholderplaceholder:tw-text-text-placeholder
shadowdefault--elevation-000tw-shadow-elevation-000
focus ringfocus--interactive-focus-outlinefocus:tw-ring-2 focus:tw-ring-[var(--interactive-focus-outline)] focus:tw-ring-offset-1
bordererror--border-danger-strongaria-[invalid=true]:tw-border-border-danger-strong
backgrounderror--background-error-weakaria-[invalid=true]:tw-bg-background-error-weak
backgrounddisabled--background-surface-layer-02disabled:tw-bg-background-surface-layer-02
textdisabled--text-disableddisabled:tw-text-text-disabled
borderdisablednone (no border)disabled:tw-border-transparent

Token Mapping — Content (dropdown)

ElementTokenTailwind class
background--background-surface-layer-01tw-bg-background-surface-layer-01
border--border-weaktw-border-border-weak
border radius10pxtw-rounded-[10px]
shadowelevation-300tw-shadow-elevation-300
padding8pxtw-p-2

Token Mapping — Item

ElementStateTokenTailwind class
textdefault--text-defaulttw-text-text-default
height32pxtw-h-8
paddingtw-px-2 tw-py-1.5
border-radius6pxtw-rounded-md
backgroundhover/focus--interactive-hoverfocus:tw-bg-interactive-hover
check iconselected--text-brandtw-text-text-brand

Token Mapping — Empty

ElementTokenTailwind class
text--text-placeholdertw-text-text-placeholder
paddingtw-py-6
alignmentcentertw-text-center

Slots

  • search icon (leading, optional, ReactNode) — defaults to Search from lucide
  • clear button (trailing, optional) — controlled via showClear prop
  • chevron trigger (trailing, optional) — controlled via showTrigger prop
  • leading icon on items (optional, ReactNode) — via children composition

States

StateTriggerNotes
defaultnormal appearance
hover--border-strong border
focusfocus ring
disabledmuted bg, text, no borderpointer-events-none
readOnlynormal appearance, no typinginput is not editable, dropdown still works
loadingspinner replaces chevronreplaces trigger icon with Spinner
errorred border + bgvia aria-invalid="true"

CVA Shape

Shape C — sizes only (no variant axis). States handled via CSS pseudo-classes and aria attributes.

Composition

Basic

jsx
<Combobox>
  <ComboboxInput placeholder="Search..." />
  <ComboboxContent>
    <ComboboxList>
      <ComboboxItem value="react">React</ComboboxItem>
      <ComboboxItem value="vue">Vue</ComboboxItem>
      <ComboboxItem value="svelte">Svelte</ComboboxItem>
    </ComboboxList>
    <ComboboxEmpty>No results found.</ComboboxEmpty>
  </ComboboxContent>
</Combobox>

With Field

jsx
<Field>
  <FieldLabel>Framework</FieldLabel>
  <Combobox>
    <ComboboxInput placeholder="Search frameworks..." />
    <ComboboxContent>
      <ComboboxList>
        <ComboboxItem value="react">React</ComboboxItem>
        <ComboboxItem value="vue">Vue</ComboboxItem>
      </ComboboxList>
      <ComboboxEmpty>No results found.</ComboboxEmpty>
    </ComboboxContent>
  </Combobox>
</Field>

With clear and groups

jsx
<Combobox>
  <ComboboxInput placeholder="Select country..." showClear />
  <ComboboxContent>
    <ComboboxList>
      <ComboboxGroup>
        <ComboboxLabel>Americas</ComboboxLabel>
        <ComboboxItem value="us">United States</ComboboxItem>
        <ComboboxItem value="ca">Canada</ComboboxItem>
      </ComboboxGroup>
      <ComboboxSeparator />
      <ComboboxGroup>
        <ComboboxLabel>Europe</ComboboxLabel>
        <ComboboxItem value="uk">United Kingdom</ComboboxItem>
        <ComboboxItem value="de">Germany</ComboboxItem>
      </ComboboxGroup>
    </ComboboxList>
    <ComboboxEmpty>No countries found.</ComboboxEmpty>
  </ComboboxContent>
</Combobox>

Notes

  • Trigger chevron: ChevronDown (lucide) — same as Select.
  • Search icon: Search (lucide) — shown as leading icon inside input.
  • Clear button: X (lucide) — shown when showClear is true and a value is selected.
  • Loading state replaces the chevron trigger with a Spinner component.
  • readOnly allows opening the dropdown to see options but prevents typing to filter.
  • Multi-select (chips) support deferred to v2 — shadcn primitive supports it via ComboboxChips.
  • Empty state shown when filter query matches no items.

Truncating long values (opt-in pattern)

Combobox itself does not auto-truncate. To reveal long content on hover, opt in by composing with TruncatingText. It uses the browser's native title attribute, so no provider or extra wiring is needed.

Option rows (in the dropdown)

ComboboxItem children render as the row label. Wrap them in TruncatingText per row — string children make auto-detection work:

jsx
<Combobox items={queries}>
  <ComboboxInput placeholder="Search query..." />
  <ComboboxContent>
    <ComboboxList>
      {(item) => (
        <ComboboxItem key={item.value} value={item}>
          <TruncatingText>{item.name}</TruncatingText>
        </ComboboxItem>
      )}
    </ComboboxList>
    <ComboboxEmpty>No results found.</ComboboxEmpty>
  </ComboboxContent>
</Combobox>

Selected value (in the input) — built in

The Combobox input trigger handles overflow itself, no opt-in required:

  • Visual ellipsiscomboboxInputVariants carries [&_input]:tw-truncate, so blurred input values that overflow show at the right edge. Focused inputs scroll horizontally per browser default.
  • Native hover tooltipComboboxInput calls the useInputOverflowTitle hook, which detects overflow on the inner input and sets input.title = input.value when overflowing (removing it when the value fits). On blur, input.scrollLeft is reset to 0 so the ellipsis reappears reliably.

Limitation: programmatic writes to input.value (Base UI updating the value after a dropdown pick) do not fire input events per HTML spec. The title attribute may be momentarily stale after a selection change until the next user interaction (typing, blur, resize). Visually the ellipsis is always correct.

Why opt-in, not built-in

Same reasoning as Select. Truncation is a layout decision per callsite. Forcing it into the primitive would require modifying the primitive itself and would couple every consumer to that decision.