React Clean UI
Form

Select

Allow users to choose one option from a dropdown list.

Select demonstration

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

Loom Preview

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.

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.

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.

Select Properties

Select-specific properties

PropertyTypeDefaultDescription
selectednumber0The zero-based index of the selected option.
onChange(selected: number, value?: string) => voidCalled when an option is selected, with the option's index and its value (if set).
scaleScaleSizeTheme defaultControls the typography scale used by the select.
spacingSpaceSizeTheme defaultControls the internal padding of the select.
max-heightCssSizeTheme defaultSets the maximum height of the dropdown before it becomes scrollable.
backgroundImageCssBackgroundImageTheme defaultSets the image layered over the closed select control's background.
backgroundGradientCssBackgroundGradientTheme defaultSets the gradient layered over the closed select control's background.
searchablebooleanfalseRenders a filter textbox above the option list when the dropdown is open.
searchPlaceholderstring"Search"Placeholder text for the search textbox. Only used when searchable is true.
childrenReact.ReactNodeThe Select.Option and Select.OptGroup components displayed in the dropdown.

Shared and native properties

PropertyTypeDefaultDescription
EventReact.InstanceEvent<ImageLabel>Native Roblox events for the underlying ImageLabel.
ChangeReact.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

PropertyTypeDefaultDescription
textstringThe option label and the value displayed when selected.
childrenReact.ReactNodeAdditional custom content rendered inside the option.
valuestringundefinedPassed as the second argument to Select's onChange when this option is selected.
EventReact.InstanceEvent<ImageButton>Roblox events for the option button.
BackgroundColor3Color3Theme colourOverrides the option's base background colour.

The index property is assigned internally by Select and should not be set manually.

Select.OptGroup Properties

PropertyTypeDefaultDescription
labelstringThe heading text rendered above the group's options.
childrenReact.ReactNodeThe 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:

  • backgroundColor and backgroundTransparency — a flat fill behind the closed control. backgroundTransparency defaults to 1 (invisible). The BackgroundColor3 and BackgroundTransparency props override them for a single select. When the theme's backgroundTransparency is below 1, the control's corners are rounded with cornerRadius so the fill matches the border. A backgroundImage draws on top of the fill.
  • textColor — colour of the selected option's text in the closed control. Unset uses theme.colors.intents.primary.default.textColor.
  • iconColor — colour of the dropdown caret icon. Unset uses theme.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.

theme.components.select.optGroup controls the appearance of Select.OptGroup headers:

  • textColor (required) — colour of the group's label text
  • typography — typography used for the group's label text
  • backgroundColor and backgroundTransparency — background of the group header row
  • spacing and padding — 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:

  • spacing and padding — margin around the search row's Input
GitHub Repository

On this page