tailwindlabs/headlessui · error · Error

Missing `options` in virtual mode

Error message

Missing `options` in virtual mode

What it means

In Headless UI React's Combobox, virtual mode is enabled by passing `virtual={...}` (a virtualizer config). A useMemo then requires the top-level `options` prop to be present so it can feed the option list to the virtualizer; if `options === undefined` while `data.virtual` is set, it throws 'Missing `options` in virtual mode'.

Source

Thrown at packages/@headlessui-react/src/components/combobox/combobox.tsx:1355

  //
  // When the `static` prop is used, we should never freeze, because rendering
  // is up to the user.
  let shouldFreeze = visible && comboboxState === ComboboxState.Closed && !props.static

  let options = useFrozenData(shouldFreeze, data.virtual?.options)

  // Frozen state, the selected value will only update visually when the user re-opens the <Combobox />
  let frozenValue = useFrozenData(shouldFreeze, data.value)

  let isSelected = useCallback(
    (compareValue: unknown) => data.compare(frozenValue, compareValue),
    [data.compare, frozenValue]
  )

  // Map the children in a scrollable container when virtualization is enabled
  let newDataContextValue = useMemo(() => {
    if (!data.virtual) return data
    if (options === undefined) throw new Error('Missing `options` in virtual mode')

    return options !== data.virtual.options
      ? { ...data, virtual: { ...data.virtual, options } }
      : data
  }, [data, options, data.virtual?.options])

  if (data.virtual) {
    Object.assign(theirProps, {
      children: (
        <ComboboxDataContext.Provider value={newDataContextValue}>
          {/* @ts-expect-error The `children` prop now is a callback function that receives `{option}` */}
          <VirtualProvider slot={slot}>{theirProps.children}</VirtualProvider>
        </ComboboxDataContext.Provider>
      ),
    })
  }

  let render = useRender()

View on GitHub (pinned to eea57cf46f)

Solutions

  1. Pass the flat options array as a prop: `<Combobox virtual={virtualizer} options={options}>`.
  2. If options load asynchronously, pass `options={options ?? []}` until loaded (or defer rendering the Combobox until data exists).
  3. Remember virtual mode requires options as data, not as <ComboboxOption> children.

Example fix

// before
<Combobox virtual={virtualizer} value={value} onChange={setValue}>
  {options.map(o => <ComboboxOption key={o.id} value={o}>...</ComboboxOption>)}
</Combobox>

// after
<Combobox virtual={virtualizer} options={options} value={value} onChange={setValue}>
  {opt => <div>{opt.label}</div>}
</Combobox>
Defensive patterns

Strategy: validation

Validate before calling

<Combobox virtual={virtualizer} options={options ?? []} ... >

Type guard

const hasOptions = (p: { options?: unknown[] }): boolean => p.options !== undefined

Prevention

When it happens

Trigger: `<Combobox virtual={virtualizer}>` without an `options` prop; passing options only via `<ComboboxOption>` children (which don't work in virtual mode); destructuring options conditionally so it becomes undefined on some renders.

Common situations: Migrating a Combobox to @tanstack/react-virtual-based virtual mode and forgetting the required `options` array; supplying options to the virtualizer object but not as the `options` prop; lazy-loading options where the first render passes undefined.

Related errors


AI-assisted analysis of tailwindlabs/headlessui@eea57cf46f (2026-08-28). Data as JSON: /api/errors/1dbc0f40eca06e50. Report an issue: GitHub.