Select
Allow users to choose one option from a dropdown list.

The <Select> component displays a dropdown menu containing a list of selectable options.
Use <Select.Option> components as direct children of <Select> to define the available choices. Related options can be grouped under a heading with <Select.OptGroup>, and the dropdown can expose a search box to filter options with the searchable property.
Import
import {
Fieldset,
Select,
Text,
} from "@rbxts/react-clean-ui";Basic Usage
Example
The Select component can be combined with a Fieldset to provide a label and consistent form layout.
<Fieldset>
<Fieldset.Label>
<Text text="Country:" />
</Fieldset.Label>
<Fieldset.Control>
<Select>
<Select.Option text="United Kingdom" />
<Select.Option text="United States" />
<Select.Option text="Canada" />
<Select.Option text="Germany" />
<Select.Option text="France" />
<Select.Option text="Australia" />
<Select.Option text="New Zealand" />
<Select.Option text="Japan" />
<Select.Option text="South Africa" />
</Select>
</Fieldset.Control>
</Fieldset>Handling Selection Changes
Use the onChange callback to receive the index of the selected option. If the selected Select.Option has a value prop, it's passed as the second argument.
function CountrySelect() {
const [selectedCountry, setSelectedCountry] =
React.useState(0);
return (
<Fieldset>
<Fieldset.Label>
<Text text="Country:" />
</Fieldset.Label>
<Fieldset.Control>
<Select
selected={selectedCountry}
onChange={(index) => {
setSelectedCountry(index);
print(`Selected option: ${index}`);
}}
>
<Select.Option text="United Kingdom" />
<Select.Option text="United States" />
<Select.Option text="Canada" />
</Select>
</Fieldset.Control>
</Fieldset>
);
}The callback receives the zero-based index of the selected option:
<Select
onChange={(index) => {
print(index);
}}
>
<Select.Option text="First" />
<Select.Option text="Second" />
<Select.Option text="Third" />
</Select>Selecting First returns 0, selecting Second returns 1, and selecting Third returns 2.
Default Selection
Use the selected property to specify which option is initially selected.
<Select selected={1}>
<Select.Option text="Small" />
<Select.Option text="Medium" />
<Select.Option text="Large" />
</Select>In this example, Medium is initially selected because it has an index of 1.
When selected is not provided, the first option is selected.
Custom Option Content
An option can contain custom children instead of only displaying text.
<Select>
<Select.Option text="Information" />
<Select.Option>
<Text
text="Custom Option"
weight="bold"
/>
</Select.Option>
</Select>You can also combine the text property with additional children.
<Select>
<Select.Option text="Standard" />
<Select.Option text="Premium">
<Text text="Recommended" />
</Select.Option>
</Select>The value shown in the closed select control is taken from the selected option's text property.
For this reason, options should normally provide text, even when they also contain custom children.
Grouping Options
Wrap a run of Select.Option components in Select.OptGroup to render a section header above them in the dropdown.
<Select>
<Select.OptGroup label="Europe">
<Select.Option text="United Kingdom" />
<Select.Option text="Germany" />
<Select.Option text="France" />
</Select.OptGroup>
<Select.OptGroup label="North America">
<Select.Option text="United States" />
<Select.Option text="Canada" />
</Select.OptGroup>
</Select>Select.OptGroup must be a direct child of Select. Selection is still index-based via selected/onChange, and indexes are assigned across all options in document order regardless of grouping. In the example above, the "Europe" options are indexes 0-2 and the "North America" options are indexes 3-4.
A Select.OptGroup with no Select.Option children doesn't render a header.
Select.Option and Select.OptGroup can also be mixed with ungrouped options:
<Select>
<Select.Option text="United Kingdom" />
<Select.OptGroup label="North America">
<Select.Option text="United States" />
<Select.Option text="Canada" />
</Select.OptGroup>
</Select>Searchable Options
Set searchable to render a filter textbox above the option list. The search box is a standard Input field, complete with its own border and corner radius, with a search icon prepended, inset with margin at the top of the dropdown. Typing filters options by a case-insensitive substring match against each option's text property.
<Select searchable>
<Select.Option text="United Kingdom" />
<Select.Option text="United States" />
<Select.Option text="Canada" />
</Select>Options that only use children, without a text property, can't be matched against the search text and are always shown.
Use searchPlaceholder to customize the search box's placeholder text, which defaults to "Search".
<Select searchable searchPlaceholder="Search countries...">
<Select.Option text="United Kingdom" />
<Select.Option text="United States" />
<Select.Option text="Canada" />
</Select>When searchable is combined with Select.OptGroup, a group's header is hidden if every option inside that group is filtered out. If no option anywhere matches the search text, the dropdown shows a "No Results" row instead of the option list.
<Select searchable>
<Select.OptGroup label="Europe">
<Select.Option text="United Kingdom" />
<Select.Option text="Germany" />
<Select.Option text="France" />
</Select.OptGroup>
<Select.OptGroup label="North America">
<Select.Option text="United States" />
<Select.Option text="Canada" />
</Select.OptGroup>
</Select>Option Events
Select.Option accepts Roblox instance events through the Event property.
<Select>
<Select.Option
text="United Kingdom"
Event={{
MouseEnter: () => {
print("Hovering United Kingdom");
},
}}
/>
<Select.Option
text="United States"
Event={{
Activated: () => {
print("United States selected");
},
}}
/>
</Select>The component preserves its internal hover and selection behaviour while calling the supplied event handlers.
Option Background Colour
Use BackgroundColor3 to provide a custom base colour for an option.
<Select>
<Select.Option
text="Standard"
BackgroundColor3={Color3.fromHex("#FFFFFF")}
/>
<Select.Option
text="Highlighted"
BackgroundColor3={Color3.fromHex("#E8F1FF")}
/>
</Select>Hover and selected-state colours may still be adjusted by the active theme.
Width
By default the select fills the full width available to it. Set width to give it a fixed or relative width instead. It accepts the same values as Container's width: a pixel number, "200px", a percentage such as "50%", or a breakpoint table. The height still fits the content.
<Select width={200}>
<Select.Option text="Small" />
<Select.Option text="Medium" />
<Select.Option text="Large" />
</Select>
<Select width="50%">
<Select.Option text="Half width" />
</Select>The dropdown opens at the control's measured width either way.
width="Auto" doesn't make the select fit its content: the select has no content width, so it collapses to zero width. Pass a pixel or percentage value instead.
Dropdown Height and Scrolling
The dropdown automatically grows to fit its options until it reaches its configured maximum height.
By default, the maximum height is taken from the active theme:
<Select>
<Select.Option text="Option 1" />
<Select.Option text="Option 2" />
<Select.Option text="Option 3" />
</Select>Use the "max-height" property to override the theme value for an individual select.
<Select max-height="300px">
<Select.Option text="Option 1" />
<Select.Option text="Option 2" />
<Select.Option text="Option 3" />
<Select.Option text="Option 4" />
<Select.Option text="Option 5" />
<Select.Option text="Option 6" />
<Select.Option text="Option 7" />
<Select.Option text="Option 8" />
<Select.Option text="Option 9" />
<Select.Option text="Option 10" />
</Select>When the options exceed the maximum height, the dropdown becomes scrollable.
When "max-height" is not provided, the value configured in theme.components.select is used.
Overlay Provider
The dropdown is rendered through the library's overlay system.
Your interface must be wrapped in an OverlayProvider for the dropdown to appear correctly.
<OverlayProvider>
<App />
</OverlayProvider>A typical application structure may look like this:
<ThemeProvider>
<OverlayProvider>
<App />
</OverlayProvider>
</ThemeProvider>The overlay allows the dropdown to render above surrounding interface elements without being clipped by parent containers.
When a Select is opened without an available overlay, the component emits a warning.
Direct Children
Select.Option components must be direct children of Select, or direct children of a Select.OptGroup that is itself a direct child of Select. Only this single level of nesting is supported—a Select.OptGroup nested inside another Select.OptGroup is not supported, and its contents are silently ignored.
<Select>
<Select.Option text="Valid Option" />
<Select.OptGroup label="Valid Group">
<Select.Option text="Also Valid" />
</Select.OptGroup>
</Select>Avoid wrapping options inside another component or frame, other than the sanctioned Select.OptGroup:
<Select>
<frame>
<Select.Option text="Invalid Option" />
</frame>
</Select>The Select component assigns option indexes while processing its direct children.
Background Image
Use backgroundImage to apply a scaled, tiled, or sliced image to the closed select control. The image is layered over the select's background colour, while its border and corner styling continue to come from the active theme.
<Select
backgroundImage={{
image: "rbxassetid://1234567890",
slice: "12 116",
}}
>
<Select.Option text="Oak" />
<Select.Option text="Walnut" />
<Select.Option text="Maple" />
</Select>The backgroundImage prop accepts a CssBackgroundImage object. Its fields match the background-image styling supported by Box, including slice, sliceScale, size, tileSize, transparency, and tintColor.
When backgroundImage is not provided, the select uses theme.components.select.backgroundImage if the active theme defines one. A directly supplied prop overrides the theme value for that select instance.
Background Gradient
Use backgroundGradient to apply a UIGradient to the closed select control. The gradient is layered over the select's background colour, while its border and corner styling continue to come from the active theme.
<Select
backgroundGradient={{
colors: [Color3.fromHex("#4F46E5"), Color3.fromHex("#EC4899")],
rotation: 45,
}}
>
<Select.Option text="Oak" />
<Select.Option text="Walnut" />
<Select.Option text="Maple" />
</Select>The backgroundGradient prop accepts a CssBackgroundGradient object. Its fields match the background-gradient styling supported by Box, including colors, stops, rotation, offset, and transparency.
When backgroundGradient is not provided, the select uses theme.components.select.backgroundGradient if the active theme defines one. A directly supplied prop overrides the theme value for that select instance.
Disabled
Set disabled to stop the select from being opened.
<Select disabled>
<Select.Option text="Oak" />
<Select.Option text="Walnut" />
</Select>A disabled select:
- doesn't open when clicked or when a paired
Fieldset.Labelis activated - closes its dropdown if it was open when
disabledbecametrue - takes the theme's
states.disabledlook, if one is set (see Theme Values)
When disabled is unset or false, the select behaves as before. disabled must be set on the Select itself: it isn't read from a surrounding Fieldset.
Native ImageLabel Properties
Select renders its root as a Container, so it extends React.InstanceProps<ImageLabel> and accepts native Roblox ImageLabel properties directly, rather than the TextBox properties its underlying control might suggest.
<Select
Position={UDim2.fromScale(0, 0.1)}
ZIndex={2}
BackgroundColor3={Color3.fromHex("#1F2937")}
>
<Select.Option text="Option One" />
</Select>See the Container documentation for the full set of native ImageLabel properties Select accepts, such as AnchorPoint, LayoutOrder, Visible, Change, and Event.
Size and AutomaticSize are fixed internally (UDim2.fromScale(1, 0) with AutomaticSize.Y — full available width, height fit to content) and are not honored if passed in. Use width to change the width.
Select Properties
Select-specific properties
| Property | Type | Default | Description |
|---|---|---|---|
selected | number | 0 | The zero-based index of the selected option. |
onChange | (selected: number, value?: string) => void | — | Called when an option is selected, with the option's index and its value (if set). |
scale | ScaleSize | Theme default | Controls the typography scale used by the select. |
spacing | SpaceSize | Theme default | Controls the internal padding of the select. |
max-height | CssSize | Theme default | Sets the maximum height of the dropdown before it becomes scrollable. |
width | ResponsiveCssSize | Full width | Sets the control's width (px, "50%", or a breakpoint table). "Auto" collapses it to zero width. |
backgroundImage | CssBackgroundImage | Theme default | Sets the image layered over the closed select control's background. |
backgroundGradient | CssBackgroundGradient | Theme default | Sets the gradient layered over the closed select control's background. |
searchable | boolean | false | Renders a filter textbox above the option list when the dropdown is open. |
searchPlaceholder | string | "Search" | Placeholder text for the search textbox. Only used when searchable is true. |
disabled | boolean | false | Prevents the dropdown from opening and applies the theme's states.disabled look. |
children | React.ReactNode | — | The Select.Option and Select.OptGroup components displayed in the dropdown. |
Shared and native properties
| Property | Type | Default | Description |
|---|---|---|---|
Event | React.InstanceEvent<ImageLabel> | — | Native Roblox events for the underlying ImageLabel. |
Change | React.InstanceChangeEvent<ImageLabel> | — | Native property-changed handlers for the underlying ImageLabel. |
All other compatible native ImageLabel properties inherited from React.InstanceProps<ImageLabel> are also accepted—see Native ImageLabel Properties above.
Select.Option Properties
| Property | Type | Default | Description |
|---|---|---|---|
text | string | — | The option label and the value displayed when selected. |
children | React.ReactNode | — | Additional custom content rendered inside the option. |
value | string | undefined | Passed as the second argument to Select's onChange when this option is selected. |
Event | React.InstanceEvent<ImageButton> | — | Roblox events for the option button. |
BackgroundColor3 | Color3 | Theme colour | Overrides the option's base background colour. |
The index property is assigned internally by Select and should not be set manually.
Select.OptGroup Properties
| Property | Type | Default | Description |
|---|---|---|---|
label | string | — | The heading text rendered above the group's options. |
children | React.ReactNode | — | The Select.Option components that belong to this group. |
Controlled Selection
The selected property is used as the component's initial selection.
After mounting, the component manages its selected index internally and calls onChange whenever the selection changes.
<Select
selected={0}
onChange={(index) => {
print(`Selection changed to ${index}`);
}}
>
<Select.Option text="One" />
<Select.Option text="Two" />
</Select>No Options
When no options are supplied, the select displays:
No Options<Select />Complete Example
import React from "@rbxts/react";
import {
Fieldset,
Select,
Text,
} from "@rbxts/react-clean-ui";
export function SettingsForm() {
const [country, setCountry] = React.useState(0);
return (
<Fieldset>
<Fieldset.Label>
<Text text="Country:" />
</Fieldset.Label>
<Fieldset.Control>
<Select
selected={country}
onChange={(index) => {
setCountry(index);
}}
>
<Select.Option text="United Kingdom" />
<Select.Option text="United States" />
<Select.Option text="Canada" />
<Select.Option text="Germany" />
<Select.Option text="France" />
<Select.Option text="Australia" />
<Select.Option text="New Zealand" />
<Select.Option text="Japan" />
<Select.Option text="South Africa" />
</Select>
</Fieldset.Control>
</Fieldset>
);
}Theme Values
The select's appearance is controlled by theme.components.select. Its backgroundImage value supplies the closed control's background image when the backgroundImage prop is omitted, and its backgroundGradient value likewise supplies the background gradient when the backgroundGradient prop is omitted. Existing select theme values continue to control typography, borders, corner radius, dropdown height, dropdown colour, and option intent colours. Internal padding (the spacing prop) is resolved from the global theme.spacing scale rather than a select-specific value.
The closed control also reads these optional values:
backgroundColorandbackgroundTransparency— a flat fill behind the closed control.backgroundTransparencydefaults to1(invisible). TheBackgroundColor3andBackgroundTransparencyprops override them for a single select. When the theme'sbackgroundTransparencyis below1, the control's corners are rounded withcornerRadiusso the fill matches the border. AbackgroundImagedraws on top of the fill.textColor— colour of the selected option's text in the closed control. Unset usestheme.colors.intents.primary.default.textColor.iconColor— colour of the dropdown caret icon. Unset usestheme.colors.intents.primary.default.textColor.
No shipped theme sets these values, so the closed control looks the same as before unless your theme sets them.
const MyTheme = extendTheme(DefaultTheme, {
components: {
select: {
backgroundColor: Color3.fromHex("#295896"),
backgroundTransparency: 0,
textColor: Color3.fromHex("#FFF7CF"),
iconColor: Color3.fromHex("#FFF7CF"),
},
},
});textColor is a separate key: the selected text doesn't use typography.color.
boxShadow, innerShadow, states, caret, and dropDown are also optional. No shipped theme sets them, and when they're unset the select looks the same as before.
Shadows
boxShadow and innerShadow work the same way as on Input and apply to the closed control:
select: {
boxShadow: { shadow: "0px 1px 0px 0px", color: Color3.fromHex("#A87A45"), transparency: 0 },
innerShadow: { color: Color3.fromHex("#000000"), transparency: 0.5, size: 8 },
}The inner shadow sits inside the control's border and draws under the selected text and caret. The outer shadow only follows cornerRadius when the control has a visible fill (theme backgroundTransparency below 1). Otherwise it is square.
Field states
states overrides the closed control's look while it is hovered, open, or disabled. Each state accepts borderColor, borderThickness, backgroundColor, backgroundTransparency, and backgroundGradient, and replaces only the keys it sets, the same as on Input.
select: {
states: {
focus: { borderColor: Color3.fromHex("#F2A65A") },
disabled: { backgroundTransparency: 0.5 },
},
}| State | Active when |
|---|---|
disabled | The disabled prop is true. |
focus | The dropdown is open. |
hover | The pointer is over the closed control. |
When more than one applies, the order of priority is disabled, then focus, then hover. The BackgroundColor3, BackgroundTransparency, and backgroundGradient props still take priority over every state.
Caret
caret puts the dropdown caret icon inside a box, like a button cap at the end of the field:
select: {
caret: {
size: 30,
backgroundColor: Color3.fromHex("#173A66"),
borderColor: Color3.fromHex("#3D2712"),
borderThickness: 2,
cornerRadius: 6,
iconColor: Color3.fromHex("#FFF7CF"),
margin: "0px -6px 0px 0px",
},
}size— width and height of the box. Defaults to the theme's icon size at the default scale.backgroundColor,backgroundTransparency, andbackgroundGradient— the box's fill. The fill is opaque whenbackgroundColororbackgroundGradientis set, and transparent otherwise, unlessbackgroundTransparencyis set.borderColorandborderThickness— an inner border. No border is drawn whenborderThicknessis0or unset.cornerRadius— the box's corner radius.iconColor— the caret icon colour. Falls back to the select'siconColor, then totheme.colors.intents.primary.default.textColor.margin— CSS-order margin (top, right, bottom, left) around the box. Values can be negative:"0px -6px 0px 0px"lets the box extend 6px into the field's right padding.
The box may overlap the field's top and bottom padding, but never its border, so it doesn't make the field taller. A Select with a caret cap is as tall as one without it (and as an Input with the same theme). The field only grows when the box is taller than the padded field, and then just enough to fit the box inside the border. The selected text or placeholder is always vertically centred in the field, whatever the box's size.
When caret is unset, the caret is drawn as a plain icon, as before.
Dropdown
dropDown overrides the open dropdown's frame separately from the closed control:
select: {
dropDown: {
cornerRadius: 8,
borderThickness: 2,
borderColor: Color3.fromHex("#3D2712"),
boxShadow: { shadow: "0px 6px 14px 0px", color: Color3.fromHex("#000000"), transparency: 0.5 },
gap: 6,
},
}cornerRadius,borderColor, andborderThickness— fall back to the select's own values when unset.boxShadow— a shadow around the dropdown, in the same shape as the select'sboxShadow. No shadow when unset.gap— space in pixels between the closed control and the dropdown. Defaults to0, so the dropdown sits directly below the control. The dropdown's border is drawn outside its frame, so it extends into the gap.
Sunk well example
These keys together produce a control that looks sunk into the panel, with a capped caret and a dropdown that floats just below it:
backgroundColor is white because the gradient multiplies the fill colour. gap: 6 leaves a 4px space plus the dropdown's 2px border. A theme extending WoodenTheme also needs backgroundImage: { image: "" } to remove Wooden's background image.
theme.components.select.optGroup controls the appearance of Select.OptGroup headers:
textColor(required) — colour of the group's label texttypography— typography used for the group's label textbackgroundColorandbackgroundTransparency— background of the group header rowspacingandpadding— internal padding of the group header row
The search row rendered when searchable is true is a standard Input with a search icon, so its typography, placeholder, and icon colour come from theme.components.input rather than a select-specific value. See the Input theme styling documentation for those fields.
theme.components.select.search only controls the outer margin around that Input within the dropdown:
spacingandpadding— margin around the search row'sInput