gridland
Components

SelectInput

A single-selection list with radio indicators and groups

A single-selection list with radio indicators, keyboard navigation, group headers, and a submitted state.

SelectInput
?

Run demo

Terminal
bunx @gridland/demo select-input
Terminal
curl -fsSL https://raw.githubusercontent.com/thoughtfulllc/gridland/main/scripts/run-demo.sh | bash -s select-input

Installation

Terminal
bunx create-gridland add select-input

Usage

import { SelectInput } from "@/components/ui/select-input"
<SelectInput
  focusId="language"
  autoFocus
  items={[
    { label: "TypeScript", value: "ts" },
    { label: "JavaScript", value: "js" },
    { label: "Python", value: "py" },
  ]}
  title="Select a language"
  onSubmit={(value) => console.log("Selected:", value)}
/>

SelectInput registers with the focus system via focusId. Wrap your app in GridlandProvider (which implicitly mounts <FocusProvider selectable>) or a standalone <FocusProvider selectable> for keyboard navigation to work. Tab to focus, Enter to select (start interacting), then use arrow keys. Escape deselects.

Examples

Controlled

Use value and onChange to control the selection externally.

Controlled
const [value, setValue] = useState<string>("ts")

<SelectInput
  focusId="language"
  autoFocus
  items={[
    { label: "TypeScript", value: "ts" },
    { label: "JavaScript", value: "js" },
    { label: "Python", value: "py" },
  ]}
  value={value}
  onChange={setValue}
  title="Select a language"
  onSubmit={(value) => console.log("Selected:", value)}
/>

Default Value

Set the initially selected item in uncontrolled mode.

Default value
<SelectInput
  focusId="language"
  autoFocus
  items={[
    { label: "TypeScript", value: "ts" },
    { label: "JavaScript", value: "js" },
    { label: "Python", value: "py" },
  ]}
  defaultValue="ts"
  title="Select a language"
  onSubmit={(value) => console.log("Selected:", value)}
/>

Groups

Use the group field on items to render group headers with separators.

With groups
<SelectInput
  focusId="tool"
  autoFocus
  items={[
    { label: "TypeScript", value: "ts", group: "Languages" },
    { label: "Python", value: "py", group: "Languages" },
    { label: "React", value: "react", group: "Frameworks" },
    { label: "Vue", value: "vue", group: "Frameworks" },
  ]}
  title="Select a tool"
  onSubmit={(value) => console.log(value)}
/>

Disabled Items

Disable individual items so they cannot be selected.

Disabled items
<SelectInput
  focusId="language"
  autoFocus
  items={[
    { label: "TypeScript", value: "ts" },
    { label: "JavaScript", value: "js", disabled: true },
    { label: "Python", value: "py" },
  ]}
  title="Select a language"
  onSubmit={(value) => console.log(value)}
/>

Required

Show a required indicator and use invalid to display an error state.

Required with validation
<SelectInput
  focusId="language"
  autoFocus
  items={[
    { label: "TypeScript", value: "ts" },
    { label: "JavaScript", value: "js" },
    { label: "Python", value: "py" },
  ]}
  required
  invalid={!hasSelected}
  title="Select a language"
  onSubmit={(value) => console.log(value)}
/>

Disabled

Disable the entire component. Navigation and submission are blocked.

Disabled
<SelectInput
  focusId="language"
  items={[
    { label: "TypeScript", value: "ts" },
    { label: "JavaScript", value: "js" },
  ]}
  disabled
  title="Select a language"
/>

Placeholder

Show placeholder text when the items list is empty.

Placeholder
<SelectInput
  focusId="language"
  autoFocus
  items={[]}
  placeholder="No options available"
  title="Select a language"
/>

Controls

  • ↑/↓ or j/k: Navigate and select
  • Enter: Submit selected item

API Reference

SelectInput

PropTypeDefaultDescription
itemsSelectInputItem<V>[][]Array of selectable items
defaultValueV-Initially selected value (uncontrolled)
valueV-Selected value (controlled)
onChange(value: V) => void-Called when selection changes
disabledbooleanfalseDisable the entire component
invalidbooleanfalseShow error state with destructive styling
errorMessagestring"Please select an option"Custom error message when invalid
requiredbooleanfalseShow required indicator (*) next to title
placeholderstring-Placeholder text when items list is empty
titlestring"Select"Title shown next to the diamond indicator
submittedStatusstring"submitted"Status text shown after submit
limitnumber12Max visible rows before scrolling
highlightColorstringtheme.primaryColor of the highlighted item
radioColorstringtheme.mutedColor of the radio indicator
onSubmit(value: V) => void-Called on Enter with the selected value
focusIdstringauto-generatedStable id for the focus system
autoFocusbooleanfalseFocus this component on mount

SelectInputItem

FieldTypeDescription
labelstringDisplay text
valueVItem value
keystring?Optional React key
groupstring?Group header label
disabledboolean?Disable individual item