Lumen Docs

Select

Custom select/dropdown. Located at apps/web/components/ui/select.tsx.

Do not use native <select>. AGENTS.md rule.

Usage

<Select
  value={position}
  onChange={(v) => setPosition(v)}
  options={[
    { value: "member",  label: "Member",  description: "Team member. Default." },
    { value: "manager", label: "Manager", description: "Leads a department." },
    { value: "ceo",     label: "CEO",     description: "Cross-dept observer. Singleton." },
  ]}
/>

Features

  • Keyboard nav (arrow keys, enter, escape)
  • Option descriptions (shown below label in muted text)
  • Disabled options (disabled: true in the option object)
  • Custom icon per option
  • Empty state when options=[]

Props

interface SelectOption {
  value: string;
  label: string;
  description?: string;
  disabled?: boolean;
  icon?: React.ReactNode;
}

interface SelectProps {
  value: string;
  onChange: (value: string) => void;
  options: SelectOption[];
  placeholder?: string;
  disabled?: boolean;
  className?: string;
}

Example: fetched options

const [models, setModels] = useState<SelectOption[]>([]);

useEffect(() => {
  api.fetch("/providers/models").then((r) => {
    setModels(
      r.models.map((m) => ({
        value: m.model,
        label: m.model,
        description: m.provider,
      }))
    );
  });
}, []);

<Select
  value={selectedModel}
  onChange={setSelectedModel}
  options={models}
  disabled={models.length === 0}
/>

Why not native

Native <select>:

  • Looks different on every browser (Chrome/Safari/Firefox)
  • Can't match our tokens (can't style option dropdowns)
  • No support for descriptions per option
  • No icon support
  • Limited keyboard nav customization

Our <Select> gives all of that at the cost of ~200 lines of code. One-time cost, consistent everywhere.

Don't

  • Don't put <Select> inside a form and submit via native form — the component doesn't bubble a synthetic change event. Use React state and submit via onClick handler.
  • Don't use for > 20 options without a search — extend with a typeahead if needed (doesn't exist yet)