suiss/ui · Pattern
Picker
A single-choice picker that renders a custom Select on desktop and a native select on mobile.
Picker is an open-source pattern from suiss/ui for React, React Native, SwiftUI and Jetpack Compose. The @esuiss/ui CLI copies its source into your project, so you own the code.
Picker lets the user choose one option from a short list. On desktop it renders a suiss/ui Select with a trigger, a popup list, labelled groups and separators. On mobile (below 48rem, via useIsMobile) it renders a NativeSelect, so the platform's own picker wheel or sheet appears; groups become optgroup elements and separators are omitted.
You describe options once with PickerItem, PickerGroup and PickerSeparator, and the same markup drives both presentations. Use it for form fields where the options are known and do not need search; for long lists, use SearchPicker.
Installation
CLI
Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:
Manual
-
Add the pieces it builds on: Native Select, Select,
use-mobile. -
Copy the source into
components/patterns/picker.tsx:
- Update the import paths to match your project.
Usage
- The value is uncontrolled by default; use
defaultValue, orvaluewithonValueChangeto control it. Pickercollects the value and label of eachPickerItemfrom its children so the desktop trigger can show the selected label. Passitemsonly when options are not rendered as direct or nestedPickerItemelements.PickerGroup,PickerItemandPickerSeparatormust be used insidePicker, or they throw.- Give the picker an accessible name with
aria-labelor aLabelthat points at itsid.
Examples
With value
Starts with an initial value through defaultValue.
Sizes
Shows the sm and default sizes side by side.
Grouped
Organizes options under labelled groups with a separator and a disabled option.
Disabled
Prevents opening and changing the value.
Invalid
Shows the invalid style through aria-invalid.
Controlled
Owns the value in state with value and onValueChange.
API Reference
Picker
The root that renders the Select or native select and holds the value.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | The selected value when controlled. |
defaultValue | string | - | The initially selected value when uncontrolled. |
onValueChange | (value: string) => void | - | Called with the new value when the user picks an option. |
placeholder | string | - | Text shown when no value is selected; on mobile it is a disabled first option. |
items | PickerItemData[] | - | Value and label pairs used to display the selected label; collected from PickerItem children when omitted. |
size | "sm" | "default" | "default" | The control height. |
id | string | - | The ID of the trigger on desktop or the native select on mobile. |
name | string | - | The field name submitted with a form. |
disabled | boolean | - | Disables the picker. |
required | boolean | - | Marks the field as required for form validation. |
className | string | - | Classes applied to the trigger on desktop or the native select on mobile. |
children | React.ReactNode | - | The PickerItem, PickerGroup and PickerSeparator elements. |
aria-invalid | boolean | - | Marks the field as invalid and applies the error style. |
aria-label | string | - | The accessible name of the control. |
aria-describedby | string | - | The ID of an element that describes the control. |
PickerGroup
Groups related options under an optional label.
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | - | The group's heading; becomes the optgroup label on mobile. |
children | React.ReactNode | - | The options in the group. |
PickerItem
A selectable option.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | Required | The value selected by this option. |
disabled | boolean | - | Prevents selecting this option. |
children | React.ReactNode | - | The option's label. |
Accessibility
- The desktop trigger has the
comboboxrole and opens alistbox; the selected option hasaria-selected, and disabled options havearia-disabled. - Labelled groups are exposed as
groupelements named by their label. - Escape closes the desktop list without changing the value.
- On mobile the control is a native
select, with the platform's built-in keyboard and screen reader support.
Notes
Styling hooks: data-slot="picker", picker-group, picker-item and picker-separator.

Picker composes Select: a SelectTrigger showing the current value and a SelectContent popup with the options. The selected option shows a check mark.
Use it to choose one value from a short or grouped list in a form.
Installation
The CLI also adds what it builds on: Select.
Manual
-
Add the pieces it builds on: Select.
-
Copy the source into
components/patterns/picker.tsx:
- Update the import paths to match your project.
-
Add the pieces it builds on: Select.
-
Copy the source into
components/patterns/picker.tsx:
- Update the import paths to match your project.
-
Add the pieces it builds on: Select.
-
Copy the source into
components/patterns/picker.tsx:
- Update the import paths to match your project.
-
Target iOS 17 or later.
-
Add the Swift package to your app:
- Copy the theme files every view uses:
- Copy the source and every file it uses:
- Add the files to your app target.
- Set
minSdkto 24 or higher and add the dependencies:
- Copy the theme files every view uses:
- Copy the source and every file it uses:
- Update the
com.esuiss.uipackage names to match your project.
Usage
List PickerItems as children, optionally inside PickerGroups separated by PickerSeparator. The picker collects the items from its children to show the selected label, or you can pass items directly.
Use it uncontrolled with defaultValue, or controlled with value and onValueChange. placeholder shows while nothing is selected.
Examples
States
A small picker with a value, an invalid picker and a disabled picker.

API Reference
Picker
The root that renders the trigger and the options popup.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | The selected value when controlled. |
defaultValue | string | - | The initial value when uncontrolled. |
onValueChange | (value: string) => void | - | Called with the new value when an option is chosen. |
placeholder | string | - | Text shown when no value is selected. |
items | PickerItemData[] | - | { value, label } pairs used to show the selected label; collected from children when omitted. |
size | "sm" | "default" | "default" | Trigger height: sm or default. |
disabled | boolean | - | Prevents opening the list. |
invalid | boolean | - | Applies the error style to the trigger. |
className | string | - | |
accessibilityLabel | string | - | Label for the trigger. |
accessibilityLabelledBy | string | - | Links the trigger to a visible label. |
children | React.ReactNode | - |
PickerGroup
Groups options under an optional label.
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | - | Heading shown above the group. |
children | React.ReactNode | - |
PickerItem
One selectable option.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | Required | The value this option selects. |
disabled | boolean | - | Prevents choosing this option. |
children | React.ReactNode | - |
Accessibility
- Give the picker an
accessibilityLabeloraccessibilityLabelledBy, since the trigger shows only the value.
Notes
- The popup uses
bg-popoverwith a subtle border that is stronger in dark mode. - The popup fades in and out, and respects the system reduce motion setting.
- On iOS the popup renders in a full-window overlay so it appears above native screens.

