KKiotaPay UI
Browse documentation

Select

A searchable single-select. It reads options from any array of objects through valueKey and labelKey, so API responses go in unmapped.

Installation

pnpm dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/select.json

Installs components/custom/select.tsx, the shadcn command and popover primitives, and form-field. It does not replace @/components/ui/select, which remains available for short static lists.

Usage

'use client'

import { Select } from '@/components/custom/select'

export function BranchField({ branches, value, onChange }) {
  return (
    <Select
      label="Branch"
      required
      data={branches}
      value={value}
      onChange={onChange}
      valueKey="id"
      labelKey="name"
      placeholder="Choose a branch"
    />
  )
}

Composite labels

labelKey accepts an array, joined with spaces — no need to map the list first.

<Select data={employees} valueKey="id" labelKey={['firstName', 'lastName']} label="Employee" />

Props

PropTypeDefaultDescription
dataFieldOption[][]Options. Any object shape.
valueunknown—Matched against option[valueKey].
onChange(value: any) => void—Receives option[valueKey].
valueKeystring'value'Property read for the stored value.
labelKeystring | string[]'label'Property or properties read for the label.
labelstring—Required.
placeholderstring'Select an option...'Shown while nothing is selected.
searchPlaceholderstring'Search...'Placeholder inside the filter box.
disabledbooleanfalseBlocks opening.
errorbooleanfalseSwitches the border and ring to the danger ramp.
errorMessageReactNode—Shown only while error is true.

Behaviour notes

Why not a native <select>. It stops being usable somewhere around fifty options, and every list this exists for — employees, branches, cost centres — is past that. The search box is the feature; the popover is what makes room for it.

Search matches the label, never the id. Nobody searches by uuid, so the Command value is the rendered label.

Scrolling stays inside the list. Wheel and touch events stop propagating once the list itself can scroll, which keeps a gesture over the options from scrolling the page — or closing a dialog that dismisses on scroll.

The trigger is a real <button>, so it is focusable, activates on Enter and Space for free, and is announced as a combobox.