Skip to content
TopClass UI

Select

One choice from a list of plain data. The popup stays inside the viewport and scrolls itself, seven rows tall by default.

client - size not measured yet

Select

client - size not measured yet

One choice from a list of plain data. The popup stays inside the viewport and scrolls itself, seven rows tall by default.

Install

Try it

size
invalid
fullWidth
placement
visibleRows
loadMoreThreshold

The code

select.tsx
import { Select } from 'topclassui/select'

<div className="grid justify-items-start gap-1.5">
  <label htmlFor="select-playground" className="text-sm text-body">Country</label>
  <Select id="select-playground" items={COUNTRIES} defaultValue="jo" />
</div>

items is plain data, so a server page can render a Select: no function crosses to the browser.

Every state

4 states
Pick a paid plan.

Import from the package

topclassui/select
import { Select } from 'topclassui/select'

Install once:npm i topclassui@beta zod

Examples

  • items takes { value, label, disabled? }: the value is what the form receives, the label what the row and the trigger show.
  • In a form it sends the chosen value, and before the page hydrates it sends the default. A form reset brings the default back.
  • The list shows seven rows, or visibleRows (1 to 20), then scrolls inside itself. It is not windowed: for thousands of options, use a Combobox.
  • placement takes the twelve shared values, bottom-start by default; the list flips at an edge and stays 8px inside the screen.
  • The width is an Input's at the same size; fullWidth fills the parent, and a width class in className lands on the trigger inside the box.
basic
form
sizes
four-rows
sixty-items
long-content
In Arabic, right to left

Props

The props of Select
PropTypeDefaultLimitsDescription
items*readonly ListOption[]--Plain data, so a Server Component can render a Select: one option per row, in order.
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 trigger, merged last, so a consumer class wins a conflict with its own.
containerBase.Portal.Props['container']--Where the list mounts; <body> by default.
fullWidthbooleanfalse-Select: the box fills the width of its parent instead of taking the width of an Input.
hasMoreboolean--
invalidbooleanfalse-Select: marks the choice as wrong: sets aria-invalid on the trigger, whose line turns to danger and thickens.
loadMore(() => void | Promise<void>)--Client-only, or a Server Action. Called once when the window nears the end.
loadMoreThresholdinteger, min 0, max 20010min 0, max 200Select: rows from the end of the open list at which loadMore is called, clamped to 0 to 200.
placeholderstring--The text of an empty trigger. Left out, the provider's select.placeholder.
placementtop | top-start | top-end | bottom | bottom-start | bottom-end | start | start-top | start-bottom | end | end-top | end-bottom"bottom-start"-Select: the side and alignment the list opens at; start and end mirror in Arabic.
refRef<HTMLButtonElement>--The trigger, a <button role="combobox">.
sizexs | sm | md | lg | xl | custom"md"-Select: the height, the inline padding and the type step of the trigger, and its width with them; custom emits no size classes.
visibleRowsnumber, min 1, max 207min 1, max 20Select: rows the list shows before it scrolls, clamped; left out, the --tc-list-rows knob applies.

Keyboard and accessibility

The keyboard of Select
KeysWhat it does
ArrowDownOpens the list on the chosen option; in the list, moves down and stops at the end.
ArrowUpOpens the list on the chosen option; in the list, moves up and stops at the start.
SpaceOpens the list on the chosen option.
EnterOpens the list; in the list, chooses the option and closes it.
HomeMoves to the first option.
EndMoves to the last option.
EscapeCloses the list and returns the focus to the trigger.
A-ZOn the closed trigger, chooses the next option that starts with the letter.
  • A name is required: id for a <label htmlFor>, or aria-label, or aria-labelledby. The trigger is a <button role="combobox">.
  • A row is 40px tall, 44px under a touch pointer, and a long label ends in an ellipsis in the row and in the trigger.
  • invalid sets aria-invalid on the trigger, whose line turns to danger and thickens.

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 trigger. The arrow keys are vertical, so they do not change.

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.