Skip to content
TopClass UI

Combobox

A text input that filters a list as you type. It is windowed above 200 options and calls loadMore for the next page.

client - size not measured yet

Combobox

client - size not measured yet

A text input that filters a list as you type. It is windowed above 200 options and calls loadMore for the next page.

Install

Try it

size
invalid
fullWidth
placement
visibleRows
hasMore
loadMoreThreshold
status

The code

combobox.tsx
import { Combobox } from 'topclassui/combobox'

<div className="grid justify-items-start gap-1">
  <label htmlFor="combobox-playground" className="text-sm font-medium text-body">Framework</label>
  <Combobox id="combobox-playground" items={FRAMEWORKS} placeholder="Search frameworks" />
</div>

Filtering runs over the options loaded so far; the next page arrives through hasMore and loadMore.

Every state

4 states

Import from the package

topclassui/combobox
import { Combobox } from 'topclassui/combobox'

Install once:npm i topclassui@beta zod

Examples

  • items takes { value, label, disabled? }: typing is matched against the label, and the form receives the value. Keep the array the same between renders.
  • The component asks and never fetches: with hasMore it calls loadMore once the open list is loadMoreThreshold rows (10) from its end, and you append the next page to items.
  • status says what is on its way: loading draws a spinner in place of the caret, loading-more writes a line under the options.
  • virtualize is auto by default: above 200 options only the rows in view and a few around them are mounted, and the highlight still reaches every option.
  • A query with no match says emptyFallback, or the provider's "no results" label. maxLength caps the query at 256 by default.
basic
form
sizes
load-more

Pages asked for: 0, loaded: 1

status
ten-thousand
long-content
In Arabic, right to left

Props

The props of Combobox
PropTypeDefaultLimitsDescription
items*readonly ListOption[]--The options, as plain data a Server Component can pass. Keep the array stable between renders.
aria-labelstring--The name read out when no visible label names the control.
aria-labelledbystring--The id of the visible text that names the control.
idstring--The id a <label htmlFor> points at.
classNamestring--On the input, merged last.
containerBase.Portal.Props['container']--Where the list mounts; the body when left out.
defaultValuestring | null--The value the page opens on, and the one a form reset returns to.
emptyFallbackReactNode--What an empty list says, in place of labels.collection.noResults.
fullWidthbooleanfalse-Fills its parent instead of taking the width of an Input.
hasMorebooleanfalse-More pages exist, so loadMore is called when the open list nears its end.
invalidbooleanfalse-Marks the value as wrong: sets aria-invalid on the input, whose line turns to danger and thickens.
keepMountedBase.Portal.Props['keepMounted']--Keeps the list mounted while it is closed.
loadMore(() => void | Promise<void>)--Called with no argument when the list nears its end while hasMore is set, never while closed. Client-only, or a Server Action.
loadMoreThresholdinteger, min 0, max 20010min 0, max 200How many rows from the end loadMore is called, clamped.
maxLengthunknown256-The cap on the typed query, 256 by default; null removes it. No counter is drawn.
onValueChange((value: string | null, details: ComboboxChan...--Called with the value of the chosen option, or null when the choice is cleared. Client-only.
placementtop | top-start | top-end | bottom | bottom-start | bottom-end | start | start-top | start-bottom | end | end-top | end-bottom"bottom-start"-The side and alignment the list opens at; start and end mirror in Arabic.
refRef<HTMLInputElement>--The visible input.
sizexs | sm | md | lg | xl | custom"md"-The height, padding and type step of the input; custom emits no size classes.
statusidle | loading | loading-more"idle"-loading draws a spinner and, with no options, says so; loading-more says so under the list.
valuestring | null--The chosen option's value, or null for none. Use with onValueChange when controlled.
virtualizeunknown"auto"-auto windows the list above 200 options; true always, false never.
visibleRowsinteger, min 1, max 207min 1, max 20Rows the list shows before it scrolls, clamped. Left out, the --tc-list-rows knob applies.

Keyboard and accessibility

The keyboard of Combobox
KeysWhat it does
ArrowDownOpens the list on its first row; in the list, moves down and wraps back to the input.
ArrowUpMoves up; past the first row it goes back to the input, then to the last row.
EnterChooses the highlighted option and closes the list.
EscapeCloses the list; on the closed input, clears the value.
ArrowLeftArrowRightMove the caret in the text.
HomeEndMove the caret to the start or the end of the text.
  • A name is required: id for a <label htmlFor>, or aria-label, or aria-labelledby. The input is an <input role="combobox">.
  • The input keeps the focus while the highlight moves through the list, as the editable combobox of the ARIA Authoring Practices does.
  • Escape on the closed input clears the value, so the form then sends an empty value.

Right to left

The caret and the tick sit at the left, and bottom-start lines the list up with the right edge of the input. ArrowLeft and ArrowRight move the caret in the text, in Arabic too.

Server and client

A client component: it renders on the server and hydrates in the browser.

Files: index.tsx, client.tsx.

On the chip: client - size not measured yet.