# Selection Choose a selection primitive from the value and interaction contract, then preserve the caller's domain value type through its public API. ## Primitive choice - [`RadioGroup`] selects one persistent field value from a visible set of options. Every `Radio` or `RadioItem` belongs to a group; do not render a standalone radio. - `SegmentedControl` selects one mode, filter, or view. It follows radio-group semantics: an active item cannot be toggled off, `Tab` enters on the selected item, and arrow keys move and select. - `Tabs` selects a panel and provides `tablist` and `tabpanel` semantics. - [`Autocomplete`] accepts free-form text with optional suggestions. - [`Combobox`] selects and remembers one or more values from a searchable collection. - [`Select`] chooses from a closed, scannable list without text entry. Multiple-selection comboboxes follow the Base UI chips composition: chips and the input share the input group, chips wrap, and the group grows vertically. For chip-based multiple comboboxes, render `ComboboxValue` around `ComboboxChips` and label the chips container only while it has the conditional `toolbar` role. Consumers must localize the `ComboboxChip` Backspace/Delete description, the item-specific `ComboboxChipRemove` name, and the input's selection count and Left Arrow hint. If `FieldDescription` already sets `aria-describedby`, put the input hint there because it takes precedence over `aria-description`. Autocomplete, Combobox, and Select popups use Base UI's `--anchor-width` and `--available-width` variables to follow their trigger while clamping to the viewport. Do not replace that sizing with a fixed width or an unclamped minimum width. Use `Radio` for the default radio appearance. Use `RadioItem` when custom content itself is a radio item, and put `RadioControl` inside it for Dify UI's visual indicator. `RadioControl` is a visual part, not a standalone radio. Import the complete family from its single public subpath: ```tsx import { Radio, RadioControl, RadioGroup, RadioItem } from '@langgenius/dify-ui/radio-group' ``` ## Typed values Do not widen domain values to `string`. Use `Select`, `RadioGroup`, `Radio`, and `RadioItem` for enums, unions, booleans, numbers, objects, or nullable placeholder values. Root generics type `value`, `defaultValue`, and value-dependent callbacks. JSX children do not inherit the parent's generic, so type independently consumed anatomy when its value cannot be inferred locally: ```tsx value={promptMode} onValueChange={setPromptMode}> value={PROMPT_MODE.default} /> value={PROMPT_MODE.custom}> Custom prompt ``` For `Select` and `Combobox`, the literal multiple-value type must match runtime mode: ` multiple>`. Value display can still receive `null` before selection. Repeat the domain type on independently consumed render anatomy rather than annotating callback parameters: ```tsx multiple value={subjects} onValueChange={setSubjects}> > {(selected) => selected?.map((subject) => subject.name).join(', ') ?? 'Anyone'} > {(subject) => {subject.name}} ``` `AutocompleteList` follows the same rule. Groups may infer their local type from `items`; a nested `Collection` is a separate JSX boundary. Dynamic `multiple={condition}` produces the corresponding single-or-multiple union. Prefer the Base UI `items` collection pattern so the root, value display, and item list share one runtime source of truth. Convert values to strings only at real serialization boundaries. ### Combobox source items and selected values Combobox has separate types for the selected business value and the source record rendered by the list: ```tsx import { Combobox, ComboboxItem, ComboboxList, createComboboxItems, } from '@langgenius/dify-ui/combobox' import { useMemo } from 'react' const userItems = useMemo( () => createComboboxItems(users, { getValue: user => user.id, getLabel: user => user.name, }), [users], ) multiple items={userItems} value={selectedUserIds} onValueChange={setSelectedUserIds} > > {user => value={user.id}>{user.name}} ``` The first generic is `Value`, the second is the literal multiple-selection mode, and the third is the source `Item`. `ComboboxValue` and `ComboboxItem` use `Value`; `filter`, `ComboboxList`, `ComboboxGroup`, `ComboboxCollection`, and `useComboboxFilteredItems` use source items. Grouped roots use the leaf record as `Item`, while the list callback receives the group object. Use `createComboboxItems` when the business contract stores a stable primitive ID but list rows need complete records. Its `getValue` result must be unique and stable, and `getLabel` owns default filtering, typeahead, and selected-value display. Create static collections at module scope and memoize collections derived from changing data. Treat the returned collection as opaque and pass it directly to `items`. For server-side search, keep the complete set of records needed to resolve selected labels in the collection passed to `items`, and pass the current result window to `filteredItems`. Filtered items are source `Item` records, not derived IDs, and grouped results must retain the collection's group shape. Keep object values when the selected record itself is the business state or the selection callback immediately needs the full record. When async refreshes may replace object references, provide `isItemEqualToValue` using the stable domain identity. `itemToStringValue` only serializes a selected `Value` for forms and autofill; it does not change `onValueChange` into an ID callback. Do not add it, `itemToStringLabel`, or a comparator as a mechanical trio. Primitive IDs normally use the default equality. For async or paged data, keep selected records in the collection when their labels must remain available after they leave the current result window, or provide an ID-only label fallback. `CheckboxGroup` follows Base UI and uses `string[]`. Model stronger business ID distinctions at the domain boundary unless the upstream primitive contract changes. [`Autocomplete`]: https://base-ui.com/react/components/autocomplete [`Combobox`]: https://base-ui.com/react/components/combobox [`RadioGroup`]: https://base-ui.com/react/components/radio [`Select`]: https://base-ui.com/react/components/select