Morphos
Inputs

Select

A custom listbox select component with keyboard navigation and ARIA combobox semantics.

Interactive example

Installation

npm install @morphos/inputs
pnpm add @morphos/inputs
yarn add @morphos/inputs
bun add @morphos/inputs

Import

import { Select } from '@morphos/inputs'

Usage

@Component()
class MyComponent extends StatefulComponent {
  render() {
    return (
      <Select
        options={[
          { value: 'react', label: 'React' },
          { value: 'solid', label: 'SolidJS' },
          { value: 'praxis', label: 'PraxisJS' },
        ]}
        placeholder="Choose a framework"
        aria-label="Frontend framework"
      />
    )
  }
}

Controlled value

@Component()
class MyComponent extends StatefulComponent {
  @State() currency = 'usd'

  render() {
    return (
      <Select
        options={[
          { value: 'usd', label: 'US Dollar' },
          { value: 'eur', label: 'Euro' },
          { value: 'gbp', label: 'British Pound' },
        ]}
        value={this.currency}
        onValueChange={(value) => (this.currency = value)}
        aria-label="Currency"
      />
    )
  }
}

Clearable with disabled options

@Component()
class MyComponent extends StatefulComponent {
  render() {
    return (
      <Select
        options={[
          { value: 'free', label: 'Free tier' },
          { value: 'pro', label: 'Pro — $9/mo' },
          { value: 'enterprise', label: 'Enterprise', disabled: true },
        ]}
        placeholder="Select a plan"
        clearable
        onClear={() => console.log('selection cleared')}
        aria-label="Pricing plan"
      />
    )
  }
}

Props

PropTypeDefaultDescription
options{ value: string; label: string; disabled?: boolean }[][]List of selectable options
valuestringControlled selected value
defaultValuestringUncontrolled initial selected value
placeholderstringText shown when no value is selected
disabledbooleanfalseDisables the entire select
requiredbooleanfalseMarks the field as required
namestringForm field name; renders a visually-hidden native <select> that mirrors the value for form submission
clearablebooleanfalseShows a clear button when a value is selected
onValueChange(value: string) => voidFires when an option is selected
onClear() => voidFires when the clear button is clicked
classstringAdditional CSS classes on the root element
idstringHTML id attribute on the root element
aria-labelstringAccessible label for the trigger button
aria-labelledbystringID of an element that labels the trigger button
aria-describedbystringID of an element that describes the trigger button

data-* attributes

AttributeElementWhen present
data-openRootThe listbox is open
data-disabledRootdisabled is true
data-placeholderTrigger buttonNo value is currently selected
data-activeOption itemThe option is currently focused via keyboard
data-selectedOption itemThe option matches the current value
data-disabledOption itemThe option has disabled: true
data-clearClear buttonAlways present on the clear button (rendered only when clearable and a value is selected)

Keyboard navigation

KeyBehavior
Enter / SpaceOpen the listbox (on the trigger) or confirm the focused option (in the listbox)
ArrowDownOpen the listbox (on the trigger) or move focus to the next option (in the listbox)
ArrowUpMove focus to the previous option
HomeMove focus to the first option
EndMove focus to the last option
EscapeClose the listbox without changing the value
TabClose the listbox and move focus to the next element

Accessibility

The trigger button has role="combobox", aria-haspopup="listbox", aria-expanded, and aria-controls pointing to the listbox. The listbox has role="listbox". Each option has role="option", aria-selected, and aria-disabled. When name is set, a visually-hidden native <select> mirrors the options and selected value so the component participates in native form submission.

If you need a native <select> element for maximum browser compatibility and mobile support, use a plain <select> instead. Select is designed for custom styling scenarios where the native control appearance is not sufficient.

Styling example

.morphos-select > button[role="combobox"][data-placeholder] {
  color: var(--morphos-color-placeholder);
}

.morphos-select > ul[role="listbox"] > li[role="option"][data-active] {
  background: var(--morphos-color-bg-hover);
}

.morphos-select > ul[role="listbox"] > li[role="option"][data-selected] {
  background: var(--morphos-color-accent);
  color: var(--morphos-color-accent-text);
}

On this page