Inkbox

Component

Combobox

The select trigger that opens a searchable list, for one value or several shown as lilac chips.

npx -y github:anwarlemu/inkbox-brand-cli#release add combobox
Preview
Multiple
Creatable
Groups
States

Usage

Use instead of Select when there are more than about ten choices or people know what they are looking for (an agent, a contact, webhook events). Wrap ComboboxTrigger (or ComboboxChips with ComboboxChip and ComboboxChipsInput for several values) and ComboboxContent with ComboboxInput, ComboboxList, ComboboxItem rows and ComboboxEmpty in a Combobox with value and onValueChange (multiple for several). Pass items so the closed field can show labels. To let people add a new value, render an extra item for the search text from useCombobox().

Avoid

  • A combobox for five or fewer choices (use Select or a radio group)
  • Animating the highlight between rows
  • A list without an empty state
  • Chips in the purple

Exports: Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxClear, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxLabel, ComboboxList, ComboboxSeparator, ComboboxTrigger, ComboboxValue, useCombobox, useComboboxAnchor. This component is a client component.

Dependencies: surface, radix-ui, cmdk, @phosphor-icons/react.

Example

examples/combobox.tsx
"use client";​import * as React from "react";import { PlusIcon, RobotIcon } from "@phosphor-icons/react/ssr";​import {  Combobox,  ComboboxChip,  ComboboxChips,  ComboboxChipsInput,  ComboboxClear,  ComboboxContent,  ComboboxEmpty,  ComboboxGroup,  ComboboxInput,  ComboboxItem,  ComboboxList,  ComboboxSeparator,  ComboboxTrigger,  ComboboxValue,  useCombobox,} from "@/components/ui/combobox";import { Label } from "@/components/ui/label";​const AGENTS = [  { value: "acme", label: "@acme" },  { value: "acme-support", label: "@acme-support" },  { value: "support-desk", label: "@support-desk" },  { value: "ops-runner", label: "@ops-runner" },  { value: "test-agent-233", label: "@test-agent-233" },  { value: "billing-bot", label: "@billing-bot", disabled: true },];​const EVENTS = ["message.received", "message.sent", "message.delivered", "message.bounced", "call.started", "call.ended", "imessage.received", "tunnel.connected", "tunnel.disconnected"];​export function Preview() {  const [agent, setAgent] = React.useState<string | null>("acme");  return (    <div className="grid w-full max-w-80 gap-1">      <Label id="agent-label">Agent</Label>      <Combobox items={AGENTS} value={agent} onValueChange={setAgent}>        <ComboboxTrigger aria-labelledby="agent-label">          <ComboboxValue placeholder="Choose an agent" />        </ComboboxTrigger>        <ComboboxContent>          <ComboboxInput placeholder="Search agents" />          <ComboboxList>            <ComboboxEmpty>No agent with that handle.</ComboboxEmpty>            {AGENTS.map((a) => (              <ComboboxItem key={a.value} value={a.value} label={a.label} disabled={a.disabled}>                <RobotIcon weight="fill" />                {a.label}              </ComboboxItem>            ))}          </ComboboxList>        </ComboboxContent>      </Combobox>    </div>  );}​export function Multiple() {  const [events, setEvents] = React.useState<string[]>(["message.received", "message.delivered"]);  return (    <div className="grid w-full max-w-100 gap-1">      <Label htmlFor="events">Events</Label>      <Combobox multiple items={EVENTS} value={events} onValueChange={setEvents} name="events">        <ComboboxChips>          {events.map((e) => (            <ComboboxChip key={e} value={e} className="font-mono text-body-sm" />          ))}          <ComboboxChipsInput id="events" placeholder={events.length ? "" : "Add events"} />          <ComboboxClear />        </ComboboxChips>        <ComboboxContent>          <ComboboxList>            {(item) => (              <ComboboxItem value={item.value} className="font-mono text-body-sm">                {item.label}              </ComboboxItem>            )}          </ComboboxList>          <ComboboxEmpty>No such event.</ComboboxEmpty>        </ComboboxContent>      </Combobox>    </div>  );}​// The "Create" row reads the search from the combobox and offers it as a new value; choosing it calls onValueChange.function CreateRow({ known }: { known: string[] }) {  const { search } = useCombobox();  const value = search.trim().toLowerCase();  if (!value || known.includes(value)) return null;  return (    <>      <ComboboxSeparator />      <ComboboxItem value={value} label={value}>        <PlusIcon />        Create &ldquo;{value}&rdquo;      </ComboboxItem>    </>  );}​export function Creatable() {  const [tags, setTags] = React.useState(["vip", "customer", "vendor"]);  const [tag, setTag] = React.useState<string | null>(null);  return (    <div className="grid w-full max-w-80 gap-1">      <Label id="tag-label">Contact tag</Label>      <Combobox items={tags} value={tag} onValueChange={(v) => {        if (v && !tags.includes(v)) setTags((t) => [...t, v]);        setTag(v);      }}>        <ComboboxTrigger aria-labelledby="tag-label">          <ComboboxValue placeholder="Add a tag" />        </ComboboxTrigger>        <ComboboxContent>          <ComboboxInput placeholder="Search or create a tag" />          <ComboboxList>            {tags.map((t) => (              <ComboboxItem key={t} value={t}>                {t}              </ComboboxItem>            ))}            <CreateRow known={tags} />          </ComboboxList>        </ComboboxContent>      </Combobox>    </div>  );}​export function Groups() {  const [line, setLine] = React.useState<string | null>(null);  return (    <div className="grid w-full max-w-80 gap-1">      <Label id="line-label">Route to</Label>      <Combobox        value={line}        onValueChange={setLine}        items={[          { value: "+16504749720", label: "+1 650 474 9720" },          { value: "+14155550134", label: "+1 415 555 0134" },          { value: "shared", label: "Shared iMessage line" },        ]}      >        <ComboboxTrigger aria-labelledby="line-label">          <ComboboxValue placeholder="Choose a line" />        </ComboboxTrigger>        <ComboboxContent>          <ComboboxInput placeholder="Search lines" />          <ComboboxList>            <ComboboxEmpty />            <ComboboxGroup heading="Phone">              <ComboboxItem value="+16504749720">+1 650 474 9720</ComboboxItem>              <ComboboxItem value="+14155550134">+1 415 555 0134</ComboboxItem>            </ComboboxGroup>            <ComboboxSeparator />            <ComboboxGroup heading="iMessage">              <ComboboxItem value="shared">Shared iMessage line</ComboboxItem>            </ComboboxGroup>          </ComboboxList>        </ComboboxContent>      </Combobox>    </div>  );}​export function States() {  return (    <div className="grid w-full max-w-80 gap-3">      <Combobox items={AGENTS} defaultValue="acme" disabled>        <ComboboxTrigger aria-label="Disabled agent" />      </Combobox>      <div className="grid gap-1">        <Combobox items={AGENTS}>          <ComboboxTrigger aria-label="Required agent" aria-invalid>            <ComboboxValue placeholder="Choose an agent" />          </ComboboxTrigger>          <ComboboxContent>            <ComboboxInput placeholder="Search agents" />            <ComboboxList>              {(a) => <ComboboxItem value={a.value}>{a.label}</ComboboxItem>}            </ComboboxList>          </ComboboxContent>        </Combobox>        <p className="text-caption leading-body text-danger">Choose the agent that owns this mailbox.</p>      </div>    </div>  );}

Source

components/ui/combobox.tsx
"use client";​import * as React from "react";import { Popover as PopoverPrimitive } from "radix-ui";import { Command as CommandPrimitive } from "cmdk";import { CaretDownIcon, CheckIcon, MagnifyingGlassIcon, XIcon } from "@phosphor-icons/react/ssr";​import { usePortalContainer } from "@/lib/surface";import { cn } from "@/lib/utils";​// A searchable choice (extrapolated from the Inkbox console, Figma 427:2715: the select trigger "Any State" and the// command menu). The trigger is the select's white 40px key with 8px corners and the Shadow/S ring, the value in 14px ink// (the placeholder in the light purple) and the bold caret at the right. It opens the menu panel (white, 8px corners,// the floating shadow) from the trigger: a 44px search row with a magnifying glass, then 36px rows of 14px ink with the// highlighted row on the lilac and a purple check beside the chosen ones; groups are headed in 13px purple. For several// values, ComboboxChips is the field itself: lilac chips with 6px corners and an x, and a text input that filters as you// type (Backspace on an empty input removes the last chip). Moving through the rows is instant: it is done by keyboard.​type Item = { value: string; label: string; disabled?: boolean };​type ComboboxContextValue = {  multiple: boolean;  values: string[];  toggle: (value: string) => void;  remove: (value: string) => void;  open: boolean;  setOpen: (open: boolean) => void;  search: string;  setSearch: (search: string) => void;  labelOf: (value: string) => string;  register: (value: string, label: string) => void;  items?: Item[];  disabled?: boolean;  listRef: React.RefObject<HTMLDivElement | null>;  chipsRef: React.RefObject<HTMLDivElement | null>;  chips: boolean;  setChips: (chips: boolean) => void;  contentId: string;};​const ComboboxContext = React.createContext<ComboboxContextValue | null>(null);​function useCombobox() {  const context = React.useContext(ComboboxContext);  if (!context) throw new Error("Combobox parts must be used within <Combobox>");  return context;}​type ComboboxBaseProps = {  /** The choices, for labels while the list is closed and for ComboboxList's render function. */  items?: (Item | string)[];  open?: boolean;  defaultOpen?: boolean;  onOpenChange?: (open: boolean) => void;  disabled?: boolean;  /** The submitted name: one hidden input per chosen value. */  name?: string;  children?: React.ReactNode;};​type ComboboxSingleProps = ComboboxBaseProps & { multiple?: false; value?: string | null; defaultValue?: string | null; onValueChange?: (value: string | null) => void };type ComboboxMultipleProps = ComboboxBaseProps & { multiple: true; value?: string[]; defaultValue?: string[]; onValueChange?: (value: string[]) => void };​const normalize = (items?: (Item | string)[]) => items?.map((i) => (typeof i === "string" ? { value: i, label: i } : i));​function Combobox(props: ComboboxSingleProps | ComboboxMultipleProps) {  const { open: openProp, defaultOpen = false, onOpenChange, disabled, name, children } = props;  const multiple = props.multiple === true;  const items = React.useMemo(() => normalize(props.items), [props.items]);  const initial = props.defaultValue == null ? [] : Array.isArray(props.defaultValue) ? props.defaultValue : [props.defaultValue];  const [inner, setInner] = React.useState<string[]>(initial);  const controlled = props.value !== undefined;  const values = controlled ? (props.value == null ? [] : Array.isArray(props.value) ? props.value : [props.value]) : inner;  const [openInner, setOpenInner] = React.useState(defaultOpen);  const open = openProp ?? openInner;  const [search, setSearch] = React.useState("");  const [chips, setChips] = React.useState(false);  const labels = React.useRef(new Map<string, string>());  const listRef = React.useRef<HTMLDivElement>(null);  const chipsRef = React.useRef<HTMLDivElement>(null);  const contentId = React.useId();​  const setOpen = React.useCallback(    (next: boolean) => {      if (openProp === undefined) setOpenInner(next);      onOpenChange?.(next);      if (!next) setSearch("");    },    [openProp, onOpenChange],  );  const commit = (next: string[]) => {    if (!controlled) setInner(next);    if (multiple) (props as ComboboxMultipleProps).onValueChange?.(next);    else (props as ComboboxSingleProps).onValueChange?.(next[0] ?? null);  };  const toggle = (value: string) => {    if (multiple) {      commit(values.includes(value) ? values.filter((v) => v !== value) : [...values, value]);      setSearch("");    } else {      // Picking the chosen value again keeps it; ComboboxClear empties the field.      commit([value]);      setOpen(false);    }  };  const remove = (value: string) => commit(values.filter((v) => v !== value));  const labelOf = (value: string) => labels.current.get(value) ?? items?.find((i) => i.value === value)?.label ?? value;  const register = React.useCallback((value: string, label: string) => labels.current.set(value, label), []);​  return (    <ComboboxContext.Provider value={{ multiple, values, toggle, remove, open, setOpen, search, setSearch, labelOf, register, items, disabled, listRef, chipsRef, chips, setChips, contentId }}>      <PopoverPrimitive.Root data-slot="combobox" open={open} onOpenChange={setOpen}>        {children}      </PopoverPrimitive.Root>      {name && values.map((v) => <input key={v} type="hidden" name={name} value={v} />)}    </ComboboxContext.Provider>  );}​const FIELD =  "flex min-h-10 w-full min-w-0 items-center gap-2 rounded-control bg-surface pr-3 pl-3.5 font-sans text-body leading-tight text-fg shadow-s outline-none transition-[box-shadow] duration-150 ease-out aria-invalid:shadow-[0_0_0_1px_var(--ds-color-status-danger)] motion-reduce:transition-none";​/** The closed field: the chosen value (ComboboxValue) and the caret. Opens the list. */function ComboboxTrigger({ className, children, ...props }: React.ComponentProps<"button">) {  const { open, disabled, values, contentId } = useCombobox();  return (    <PopoverPrimitive.Trigger asChild>      <button        type="button"        role="combobox"        aria-expanded={open}        aria-controls={contentId}        data-slot="combobox-trigger"        data-empty={!values.length}        disabled={disabled}        className={cn(FIELD, "h-10 cursor-pointer text-left focus-visible:shadow-[var(--ds-shadow-s),var(--ds-shadow-focus)] disabled:cursor-not-allowed disabled:bg-subtle disabled:text-fg-muted data-[empty=true]:text-fg-subtle", className)}        {...props}      >        <span className="flex min-w-0 flex-1 items-center gap-2 truncate">{children ?? <ComboboxValue />}</span>        <CaretDownIcon aria-hidden weight="bold" className="size-4 shrink-0 text-fg" />      </button>    </PopoverPrimitive.Trigger>  );}​/** The chosen value's label, the chosen labels joined, or the placeholder. */function ComboboxValue({ placeholder = "Select", children }: { placeholder?: string; children?: (values: string[]) => React.ReactNode }) {  const { values, labelOf } = useCombobox();  if (children) return <>{children(values)}</>;  return <span data-slot="combobox-value">{values.length ? values.map(labelOf).join(", ") : placeholder}</span>;}​/** Clears every chosen value. Put it inside ComboboxChips or beside a trigger. */function ComboboxClear({ className, ...props }: React.ComponentProps<"button">) {  const { values, remove, disabled } = useCombobox();  if (!values.length) return null;  return (    <button      type="button"      data-slot="combobox-clear"      aria-label="Clear"      disabled={disabled}      onClick={(e) => {        e.stopPropagation();        values.forEach(remove);      }}      className={cn("inline-flex size-6 shrink-0 items-center justify-center rounded-sm text-fg-faint outline-none transition-colors duration-150 ease-out hover:text-fg-muted focus-visible:outline-2 focus-visible:outline-focus-ring motion-reduce:transition-none", className)}      {...props}    >      <XIcon weight="bold" className="size-4" />    </button>  );}​function ComboboxContent({ className, children, align = "start", sideOffset = 6, anchor, onInteractOutside, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content> & { anchor?: React.RefObject<HTMLElement | null> }) {  const container = usePortalContainer();  const { search, chips, listRef, chipsRef, contentId, open } = useCombobox();  // With chips cmdk does not filter, so it would keep a hidden row highlighted: highlight the first match instead.  const [active, setActive] = React.useState("");  React.useEffect(() => {    if (!chips || !open) return;    const frame = requestAnimationFrame(() => setActive(listRef.current?.querySelector("[cmdk-item]:not([data-disabled=true])")?.getAttribute("data-value") ?? ""));    return () => cancelAnimationFrame(frame);  }, [chips, open, search, listRef]);  return (    <>    {anchor && <PopoverPrimitive.Anchor virtualRef={anchor as React.RefObject<HTMLElement>} />}    <PopoverPrimitive.Portal container={container}>      <PopoverPrimitive.Content        id={contentId}        data-slot="combobox-content"        align={align}        sideOffset={sideOffset}        // With chips the typing stays in the field; the list follows it.        onOpenAutoFocus={chips ? (e) => e.preventDefault() : undefined}        onInteractOutside={(e) => {          onInteractOutside?.(e);          // A press in the chips field keeps the list open: the field is where the typing happens.          if (chips && chipsRef.current?.contains(e.target as Node)) e.preventDefault();        }}        className={cn(          "z-50 w-(--radix-popover-trigger-width) min-w-56 max-w-[calc(100vw-2rem)] origin-(--radix-popover-content-transform-origin) overflow-hidden rounded-control bg-surface text-fg shadow-floating outline-none data-[state=closed]:animate-pop-out data-[state=open]:animate-pop-in",          className,        )}        {...props}      >        <CommandPrimitive ref={listRef} shouldFilter={!chips} {...(chips ? { value: active, onValueChange: setActive } : {})} className="flex flex-col">          {children}        </CommandPrimitive>      </PopoverPrimitive.Content>    </PopoverPrimitive.Portal>    </>  );}​/** The search row at the top of the list. */function ComboboxInput({ className, placeholder = "Search", ...props }: Omit<React.ComponentProps<typeof CommandPrimitive.Input>, "value" | "onValueChange">) {  const { search, setSearch } = useCombobox();  return (    <div data-slot="combobox-input-wrapper" className="flex h-11 shrink-0 items-center gap-2.5 border-b border-line-subtle px-3.5">      <MagnifyingGlassIcon aria-hidden className="size-5.5 shrink-0 text-fg-muted" />      <CommandPrimitive.Input        data-slot="combobox-input"        value={search}        onValueChange={setSearch}        placeholder={placeholder}        className={cn("h-full w-full min-w-0 bg-transparent font-sans text-body leading-tight text-fg outline-none placeholder:text-fg-subtle", className)}        {...props}      />    </div>  );}​/** The rows. Pass items as children, or a function that renders each of the Combobox's items. */function ComboboxList({ className, children, ...props }: Omit<React.ComponentProps<typeof CommandPrimitive.List>, "children"> & { children?: React.ReactNode | ((item: Item) => React.ReactNode) }) {  const { items } = useCombobox();  return (    <CommandPrimitive.List data-slot="combobox-list" className={cn("max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto overscroll-contain p-1", className)} {...props}>      {typeof children === "function" ? items?.map((item) => <React.Fragment key={item.value}>{children(item)}</React.Fragment>) : children}    </CommandPrimitive.List>  );}​/** Renders a group's own items with a function, inside a ComboboxGroup. */function ComboboxCollection({ items, children }: { items: (Item | string)[]; children: (item: Item) => React.ReactNode }) {  return <>{normalize(items)?.map((item) => <React.Fragment key={item.value}>{children(item)}</React.Fragment>)}</>;}​function ComboboxItem({ value, label, className, children, disabled, ...props }: Omit<React.ComponentProps<typeof CommandPrimitive.Item>, "value" | "onSelect"> & { value: string; label?: string }) {  const { values, toggle, register, chips, search } = useCombobox();  const text = label ?? (typeof children === "string" ? children : value);  React.useEffect(() => {    register(value, text);  }, [register, value, text]);  // With chips, cmdk does not filter (the typing is outside it): hide rows that do not match here.  if (chips && search && !text.toLowerCase().includes(search.toLowerCase())) return null;  const selected = values.includes(value);  return (    <CommandPrimitive.Item      data-slot="combobox-item"      value={`${text} ${value}`}      disabled={disabled}      data-checked={selected || undefined}      onSelect={() => toggle(value)}      className={cn(        "relative flex h-9 cursor-default items-center gap-2.5 rounded-control pr-8 pl-2.5 font-sans text-body leading-tight text-fg outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-subtle [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5.5 [&_svg:not([class*='text-'])]:text-icon-secondary",        className,      )}      {...props}    >      {children ?? text}      {selected && <CheckIcon aria-hidden weight="bold" className="absolute right-2.5 size-4 text-fg-brand" />}    </CommandPrimitive.Item>  );}​function ComboboxEmpty({ className, children = "No results.", ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {  const { chips, search, items, labelOf, values } = useCombobox();  // cmdk's own empty state only knows its filter; with chips, decide here.  if (chips) {    const any = (items ?? values.map((v) => ({ value: v, label: labelOf(v) }))).some((i) => !search || i.label.toLowerCase().includes(search.toLowerCase()));    if (any) return null;    return (      <div data-slot="combobox-empty" className={cn("py-6 text-center text-body text-fg-muted", className)}>        {children}      </div>    );  }  return (    <CommandPrimitive.Empty data-slot="combobox-empty" className={cn("py-6 text-center text-body text-fg-muted", className)} {...props}>      {children}    </CommandPrimitive.Empty>  );}​function ComboboxGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {  return (    <CommandPrimitive.Group      data-slot="combobox-group"      className={cn("overflow-hidden text-fg [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:pt-2 [&_[cmdk-group-heading]]:pb-1 [&_[cmdk-group-heading]]:text-body-sm [&_[cmdk-group-heading]]:leading-tight [&_[cmdk-group-heading]]:text-fg-label", className)}      {...props}    />  );}​/** A heading inside the list that is not a group's (a note above the rows). */function ComboboxLabel({ className, ...props }: React.ComponentProps<"div">) {  return <div data-slot="combobox-label" className={cn("px-2.5 pt-2 pb-1 text-body-sm leading-tight text-fg-label", className)} {...props} />;}​function ComboboxSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {  return <CommandPrimitive.Separator data-slot="combobox-separator" className={cn("-mx-1 my-1 h-px bg-line-subtle", className)} {...props} />;}​/** The field for several values: chips and a text input, anchoring the list. */function ComboboxChips({ className, children, ...props }: React.ComponentProps<"div">) {  const { setChips, setOpen, disabled, chipsRef } = useCombobox();  React.useEffect(() => {    setChips(true);    return () => setChips(false);  }, [setChips]);  return (    <PopoverPrimitive.Anchor asChild>      <div        ref={chipsRef}        data-slot="combobox-chips"        data-disabled={disabled || undefined}        onMouseDown={(e) => {          if (disabled || e.target !== e.currentTarget) return;          e.preventDefault();          e.currentTarget.querySelector("input")?.focus();          setOpen(true);        }}        className={cn(FIELD, "flex-wrap gap-1.5 py-1.5 has-[input:focus-visible]:shadow-[var(--ds-shadow-s),var(--ds-shadow-focus)] data-disabled:bg-subtle data-disabled:text-fg-muted", className)}        {...props}      >        {children}      </div>    </PopoverPrimitive.Anchor>  );}​function ComboboxChip({ value, className, children, ...props }: React.ComponentProps<"span"> & { value: string }) {  const { remove, labelOf, disabled } = useCombobox();  return (    <span data-slot="combobox-chip" className={cn("inline-flex h-7 max-w-full items-center gap-1 rounded-sm bg-subtle pr-1 pl-2 text-body leading-tight text-fg", className)} {...props}>      <span className="truncate">{children ?? labelOf(value)}</span>      <button        type="button"        aria-label={`Remove ${labelOf(value)}`}        disabled={disabled}        onClick={() => remove(value)}        className="inline-flex size-5 items-center justify-center rounded-xs text-fg-muted outline-none transition-colors duration-150 ease-out hover:bg-subtle-hover hover:text-fg focus-visible:outline-2 focus-visible:outline-focus-ring motion-reduce:transition-none"      >        <XIcon weight="bold" className="size-3.5" />      </button>    </span>  );}​/** The typing part of ComboboxChips: filters the list; arrows and Enter work the list; Backspace removes the last chip. */function ComboboxChipsInput({ className, onKeyDown, ...props }: React.ComponentProps<"input">) {  const { search, setSearch, setOpen, open, values, remove, listRef, disabled, contentId } = useCombobox();  return (    <input      data-slot="combobox-chips-input"      role="combobox"      aria-expanded={open}      aria-controls={contentId}      aria-autocomplete="list"      disabled={disabled}      value={search}      onChange={(e) => {        setSearch(e.target.value);        if (!open) setOpen(true);      }}      onFocus={() => setOpen(true)}      onKeyDown={(e) => {        onKeyDown?.(e);        if (e.defaultPrevented) return;        if (e.key === "Backspace" && !search && values.length) remove(values[values.length - 1]);        else if (["ArrowDown", "ArrowUp", "Enter", "Home", "End"].includes(e.key) && open && listRef.current) {          e.preventDefault();          // Hand the key to the list, which moves the highlight or picks the highlighted row.          listRef.current.dispatchEvent(new KeyboardEvent("keydown", { key: e.key, bubbles: true, cancelable: true }));        } else if (e.key === "ArrowDown" && !open) setOpen(true);      }}      className={cn("h-7 min-w-24 flex-1 bg-transparent font-sans text-body leading-tight text-fg outline-none placeholder:text-fg-subtle disabled:cursor-not-allowed", className)}      {...props}    />  );}​/** A ref for an element the list should open from, when it is not the trigger (pass it to ComboboxContent's anchor). */function useComboboxAnchor<T extends HTMLElement = HTMLDivElement>() {  return React.useRef<T>(null);}​export {  Combobox,  ComboboxChip,  ComboboxChips,  ComboboxChipsInput,  ComboboxClear,  ComboboxCollection,  ComboboxContent,  ComboboxEmpty,  ComboboxGroup,  ComboboxInput,  ComboboxItem,  ComboboxLabel,  ComboboxList,  ComboboxSeparator,  ComboboxTrigger,  ComboboxValue,  useCombobox,  useComboboxAnchor,};

Reference: Extrapolated from the Inkbox console (Figma 427:2715): the select trigger (Any State), the command menu and the lilac handle chip.