@xsolla/xui-select
v0.216.0
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 single-value dropdown input that displays the current selec
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 single-value dropdown input that displays the current selection and opens a ContextMenu panel when clicked. Supports five sizes, an optional leading icon, a clear button, and an icon-only mode. The dropdown panel is powered by ContextMenu — see its documentation for option list composition, search, and behaviour.
When to use
- When the user must pick exactly one value from a fixed, mutually exclusive list — country, currency, status, category
- When the list of options is known, finite, and does not require typing to navigate
- When vertical space is limited and showing every option (as with radio buttons) would be too large
- When the list is too long to show inline — use Select instead of radio buttons when there are more than 5–6 options
- In forms, filter bars, and settings panels where a compact single-line field is needed
- As part of a larger form alongside other controls
When not to use
- When the user can choose more than one value — use Multiselect instead
- When the list is long or the user benefits from typing to filter — use Autocomplete / Combobox instead
- When the value is freeform text the user must type — use TextArea or Input instead
- When there are only 2–3 options that should always stay visible — use Radio buttons or ToggleButtonGroup instead
- When the choice is a simple on/off — use a Switch or Checkbox instead
- When the value is time or date — use InputTime or DatePicker instead
Content guidelines
- Label text (placeholder) — use a concise instructional phrase: "Select language", "Choose currency", "Pick a category". Avoid generic placeholders like "Select…" alone when a more specific hint is possible.
- Option labels — label each option clearly and concisely — one short phrase, no trailing punctuation. Use parallel structure across all options in the same list (all nouns, or all short phrases). Avoid negative options (e.g. "Do not notify me") — rephrase positively.
- Option order — list options in a logical order: most common first, alphabetical, or by value. Never randomise.
- Option descriptions — use the ContextMenu Description slot to add clarifying context when the label alone is not sufficient — e.g. label "Admin", description "Full access to all settings".
- Selected value display — show the full option label as selected. If the label is very long and the field is narrow, truncate with ellipsis and show the full label in a tooltip on hover.
- Field label — always provide a visible label above the Select via FieldGroup. Do not rely on the placeholder text alone as the field identifier.
- Error messages — be specific:
- "Please select a language" (required, empty on submit)
- "Selected option is no longer available" (stale value)
Icon choice — use a left icon that represents the category being selected, not a generic dropdown indicator. The chevron already signals "this opens a list."
Behaviour guidelines
- Single selection — only one value can be selected at a time. Selecting a new option automatically replaces the previous selection. The value cannot be "unselected" by clicking it again — to clear the selection, provide an explicit clear button (Clear button=true) or a "None" / "No selection" option in the list.
- Opening — clicking the field or pressing Enter / Space / ↓ when focused opens the ContextMenu. The field transitions to State=Focus; the chevron rotates to point upward.
- Closing — the ContextMenu closes on: selecting an option, pressing Escape, clicking outside, or pressing Tab past the last option. On close, focus returns to the Select field.
- Selection — when the user selects an option from the ContextMenu, the field updates to Filled=True and the label shows the selected option's text. The dropdown closes immediately.
- Clear — clicking the clear button (✕) clears the selection (Filled=False), closes the dropdown if open, and returns focus to the field.
- Validation — validate on blur (when the field loses focus without a selection) or on form submit. Switch to State=Error with a specific message if a required field has no value. Clear the error when a value is selected.
- Placeholder — show placeholder text in Filled=False using Label text. The placeholder should describe what to select (e.g. "Select a country", "Choose language") rather than repeating the field label.
- Disabled — State=Disable prevents opening the dropdown. If a value is present, it remains visible in muted style. Provide a tooltip or nearby explanation if the reason for disabling is not obvious.
- ContextMenu size matching — the ContextMenu panel width should match or exceed the Select field width. Never open a narrower ContextMenu than the trigger field — options would be cut off.
Accessibility
- Select must render as a (or a with role="combobox") with aria-haspopup="listbox" and aria-expanded="true" / "false" reflecting the open state.
- The field must have an accessible name via (from FieldGroup), aria-label, or aria-labelledby.
- When Icon only=True, aria-label on the field is mandatory — the label is hidden and the icon alone conveys no text to screen readers.
- When Filled=True, reflect the selected value in aria-label or ensure it is part of the field's visible text that screen readers can read: e.g. aria-label="Language: English".
- The clear button must have aria-label="Clear selection" or aria-label="Clear [field name]".
- When State=Error, the error message must be linked via aria-describedby and placed in an aria-live="polite" region.
- Keyboard navigation:
- Enter / Space / ↓ opens the dropdown
- ↑ / ↓ navigate options inside the ContextMenu
- Enter selects the focused option
- Escape closes without selecting; focus returns to the field
- Tab closes and moves to the next focusable element
The left icon must have aria-hidden="true" when a label is also visible.
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
