@xsolla/xui-select
v0.185.1
Published
A cross-platform React select component for choosing from a list of predefined options with dropdown menu. Works on both React (web) and React Native. <!-- BEGIN:xui-mcp-instructions:select --> A Select lets users choose a single value from a predefined l
Readme
Select
A cross-platform React select component for choosing from a list of predefined options with dropdown menu. Works on both React (web) and React Native.
A Select lets users choose a single value from a predefined list of options. It is commonly used in forms, filters, settings, and toolbars where the set of valid choices is known in advance.
When to use
When the user must pick exactly one value from a fixed, mutually exclusive list (country, currency, status, etc.)
- When the list of options is known, finite, and does not require typing
- When vertical space is limited and showing every option (as with radio buttons) would be too large
- As part of a larger form alongside other controls
When not to use
- When the user can choose more than one value — use a MultiSelect instead
- When the list is long or the user benefits from typing to filter — use an Autocomplete instead
- When the value is freeform text the user must type — use a TextArea instead
- When there are only two or three options that should always stay visible — use Radio buttons or a Segmented instead
- When the choice is a simple on/off — use a Switch or Checkbox instead
- When the value is time or date — use an InputTime or DatePicker instead
Content guidelines
Label each option clearly and concisely — one short phrase, no trailing punctuation.
Use parallel structure across all options in the same group (all nouns, or all short phrases).
Use Description to add clarifying context when the label alone is not sufficient.
List options in a logical order — most common first, alphabetical, or by value.
Avoid negative options (e.g. "Do not notify me") — rephrase positively.
Behaviour guidelines
Only one radio in a group can be checked at a time — selecting one automatically unchecks the others.
A radio cannot be unchecked by clicking it again. To clear a selection, a "None" or "Reset" option must be provided explicitly.
Disable items remain visible but cannot be interacted with.
The Error state is typically applied to all items in the group (or to the group container) when validation fails — not to individual items.
Accessibility
Wrap the group in a with a that names the group, or use role="radiogroup" with aria-labelledby.
Each radio input should use role="radio" with aria-checked="true" or "false".
Apply aria-disabled="true" to disabled items.
Error messages should be linked to the group via aria-describedby.
Never rely on color alone to communicate the error state — always show the error message text.
Installation
npm install @xsolla/xui-selectDemo
Basic Select
import * as React from "react";
import { Select } from "@xsolla/xui-select";
export default function BasicSelect() {
const [value, setValue] = React.useState("");
return (
<Select
value={value}
onChange={setValue}
options={["Option 1", "Option 2", "Option 3"]}
placeholder="Select an option"
/>
);
}Select with Label
import * as React from "react";
import { Select } from "@xsolla/xui-select";
export default function LabeledSelect() {
const [country, setCountry] = React.useState("");
return (
<Select
label="Country"
value={country}
onChange={setCountry}
options={[
"United States",
"Canada",
"United Kingdom",
"Germany",
"France",
]}
placeholder="Select a country"
/>
);
}Select with Object Options
import * as React from "react";
import { Select } from "@xsolla/xui-select";
export default function ObjectOptionsSelect() {
const [status, setStatus] = React.useState("");
const options = [
{ label: "Active", value: "active" },
{ label: "Pending", value: "pending" },
{ label: "Inactive", value: "inactive" },
];
return (
<Select
label="Status"
value={status}
onChange={setStatus}
options={options}
placeholder="Select status"
/>
);
}Select Sizes
import * as React from "react";
import { Select } from "@xsolla/xui-select";
export default function SelectSizes() {
const options = ["Small", "Medium", "Large"];
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<Select size="xs" options={options} placeholder="Extra Small" />
<Select size="sm" options={options} placeholder="Small" />
<Select size="md" options={options} placeholder="Medium (default)" />
<Select size="lg" options={options} placeholder="Large" />
<Select size="xl" options={options} placeholder="Extra Large" />
</div>
);
}Select with Icons
import * as React from "react";
import { Select } from "@xsolla/xui-select";
import { Globe } from "@xsolla/xui-icons-base";
export default function SelectWithIcons() {
return (
<Select
iconLeft={<Globe />}
options={["English", "Spanish", "French", "German"]}
placeholder="Select language"
/>
);
}Anatomy
Import the component and use it directly:
import { Select } from "@xsolla/xui-select";
<Select
label="Field Label" // Optional label above select
value={value} // Controlled selected value
onChange={setValue} // Value change handler
options={["A", "B", "C"]} // Array of options (strings or objects)
placeholder="Select..." // Placeholder text
iconLeft={<Icon />} // Optional left icon
iconRight={<Icon />} // Optional right icon (default: ChevronDown)
filled // Whether to show filled background
state="error" // State: "default" | "disable" | "error"
errorMessage="Error message" // Error message (shown when state="error")
/>;Examples
Error State
import * as React from "react";
import { Select } from "@xsolla/xui-select";
export default function ErrorSelect() {
return (
<Select
label="Required Field"
state="error"
errorMessage="Please select an option"
options={["Option 1", "Option 2", "Option 3"]}
placeholder="Select an option"
/>
);
}Disabled Select
import * as React from "react";
import { Select } from "@xsolla/xui-select";
export default function DisabledSelect() {
return (
<Select
label="Disabled Field"
value="Option 1"
state="disable"
options={["Option 1", "Option 2", "Option 3"]}
/>
);
}Unfilled/Transparent Background
import * as React from "react";
import { Select } from "@xsolla/xui-select";
export default function UnfilledSelect() {
return (
<Select
filled={false}
options={["Option 1", "Option 2", "Option 3"]}
placeholder="Transparent background"
/>
);
}Icon Only Select
import * as React from "react";
import { Select } from "@xsolla/xui-select";
import { Settings } from "@xsolla/xui-icons";
export default function IconOnlySelect() {
return (
<Select
iconOnly
iconLeft={<Settings />}
options={["Settings", "Preferences", "Advanced"]}
/>
);
}Full Width Select
import * as React from "react";
import { Select } from "@xsolla/xui-select";
export default function FullWidthSelect() {
return (
<div style={{ display: "flex", gap: 16 }}>
<Select
fullWidth
options={["Option 1", "Option 2", "Option 3"]}
placeholder="Stretches to fill container"
/>
<Select
fullWidth={false}
options={["Option 1", "Option 2", "Option 3"]}
placeholder="Intrinsic width"
/>
</div>
);
}Custom Popover Width
By default the dropdown popover matches the trigger width. Use popoverWidth to
size the popover independently — handy when option labels are wider than the
field. Pass a number (pixels) or any CSS width string.
import * as React from "react";
import { Select } from "@xsolla/xui-select";
export default function CustomPopoverWidth() {
return (
<div style={{ width: 200 }}>
<Select
popoverWidth={320}
options={[
"Account overview and billing details",
"Subscription management",
"Payment history and transactions",
]}
placeholder="Custom popover width"
/>
</div>
);
}Form with Select
import * as React from "react";
import { Select } from "@xsolla/xui-select";
import { Input } from "@xsolla/xui-input";
import { Button } from "@xsolla/xui-button";
export default function FormWithSelect() {
const [form, setForm] = React.useState({
name: "",
category: "",
priority: "",
});
return (
<div
style={{ display: "flex", flexDirection: "column", gap: 16, width: 300 }}
>
<Input
label="Task Name"
value={form.name}
onChangeText={(name) => setForm((prev) => ({ ...prev, name }))}
placeholder="Enter task name"
/>
<Select
label="Category"
value={form.category}
onChange={(category) => setForm((prev) => ({ ...prev, category }))}
options={["Development", "Design", "Marketing", "Sales"]}
placeholder="Select category"
/>
<Select
label="Priority"
value={form.priority}
onChange={(priority) => setForm((prev) => ({ ...prev, priority }))}
options={[
{ label: "High", value: "high" },
{ label: "Medium", value: "medium" },
{ label: "Low", value: "low" },
]}
placeholder="Select priority"
/>
<Button>Create Task</Button>
</div>
);
}Dropdown Positioning
By default the dropdown always opens on the side (default "bottom"). Pass autoFlip to let it flip to the opposite side when there isn't enough viewport space.
import * as React from "react";
import { Select } from "@xsolla/xui-select";
const options = ["Option 1", "Option 2", "Option 3"];
// Always opens below (default — no surprises for existing layouts)
<Select options={options} placeholder="Default" />
// Always opens above
<Select side="top" options={options} placeholder="Opens above" />
// Opens below, flips above when near the bottom of the viewport
<Select autoFlip options={options} placeholder="Auto-flip enabled" />
// Opens above, flips below when near the top of the viewport
<Select side="top" autoFlip options={options} placeholder="Prefers top, flips if needed" />API Reference
Select
The main select component. Renders a button trigger with dropdown menu.
Select Props:
| Prop | Type | Default | Description |
| :---------------- | :-------------------------------------------------------- | :---------------- | :------------------------------------------------------------------------------------------------------------ |
| testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. |
| value | string | - | The controlled selected value. |
| placeholder | string | "Select" | Placeholder text when no value selected. |
| onPress | () => void | - | Callback when select trigger is pressed. |
| size | "xl" \| "lg" \| "md" \| "sm" \| "xs" | "md" | The size of the select. |
| state | "default" \| "hover" \| "focus" \| "disable" \| "error" | "default" | The visual state of the select. |
| label | string | - | Label text displayed above the select. |
| disabled | boolean | false | Whether the select is disabled. Also triggered when state="disable". |
| errorMessage | string | - | Error message displayed below the select (also sets error styling). |
| iconLeft | ReactNode | - | Icon displayed on the left side. |
| iconRight | ReactNode | <ChevronDown /> | Icon displayed on the right side. |
| filled | boolean | true | Whether to show filled background. |
| iconOnly | boolean | false | Whether to show only icon without text. |
| options | (string \| SelectOption)[] | [] | Array of options to display. |
| onChange | (value: string) => void | - | Callback when value changes. |
| searchable | boolean | false | Whether to show a search input in the dropdown. |
| searchPlaceholder | string | "Search" | Placeholder text for the search input. |
| noOptionsMessage | string | "No results" | Message shown when no options match the search. |
| clearable | boolean | false | Show a clear button to reset the selected value (field variant only). |
| onClear | () => void | - | Callback when the clear button is pressed. |
| maxHeight | number | 300 | Maximum height of the dropdown in pixels. |
| popoverWidth | number \| string | "100%" | Width of the dropdown popover/menu. Defaults to the trigger width; pass a number (px) or CSS width string to size it independently of the trigger. |
| fullWidth | boolean | true | Whether the select should stretch to fill the full width of its container. |
| overlayThemeMode | ThemeMode | themeMode | Theme mode for the dropdown overlay. |
| overlayThemeProductContext | ProductContext | themeProductContext | Product context for the dropdown overlay. |
| side | "top" \| "bottom" | "bottom" | Preferred side for the dropdown. |
| autoFlip | boolean | false | When true, the dropdown flips to the opposite side when there isn't enough viewport space on the preferred side. |
SelectOption Type:
interface SelectOption {
label: string; // Display text
value: any; // Value to be selected
disabled?: boolean; // Whether this option is disabled
}React Native Notes
The Select component works on React Native with the following differences:
| Feature | Web | React Native |
| --------------------- | ----------------------------- | ----------------------------------- |
| Searchable dropdown | Supported (searchable prop) | Not available — prop is ignored |
| Dropdown shadow | CSS box-shadow | Android elevation: 4 |
| Scroll behavior | overflowY: auto | Native ScrollView |
| Cursor styles | pointer / not-allowed | Ignored |
| Click-outside dismiss | document.addEventListener | Not active (guarded by isNative) |
Accessibility
- Trigger has
role="combobox",aria-haspopup="listbox", andaria-expanded(updates on open/close) - Trigger links to its label via
aria-labelledbywhen thelabelprop is set - Dropdown has
role="listbox"; each option hasrole="option"andaria-selected - Disabled trigger and disabled options expose
aria-disabled="true" - Search input has
aria-labelmatchingsearchPlaceholder - Selected item auto-scrolls into view when the dropdown opens (web only)
- Disabled state properly communicated to assistive technology
