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 Previewv0.13.2

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.

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.Label is activated
  • closes its dropdown if it was open when disabled became true
  • takes the theme's states.disabled look, 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

PropertyTypeDefaultDescription
selectednumber0The 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).
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.
widthResponsiveCssSizeFull widthSets the control's width (px, "50%", or a breakpoint table). "Auto" collapses it to zero width.
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.
disabledbooleanfalsePrevents the dropdown from opening and applies the theme's states.disabled look.
childrenReact.ReactNode—The 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
textstring—The option label and the value displayed when selected.
childrenReact.ReactNode—Additional 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
labelstring—The heading text rendered above the group's options.
childrenReact.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:

  • 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.

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 },
    },
}
StateActive when
disabledThe disabled prop is true.
focusThe dropdown is open.
hoverThe 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, and backgroundGradient — the box's fill. The fill is opaque when backgroundColor or backgroundGradient is set, and transparent otherwise, unless backgroundTransparency is set.
  • borderColor and borderThickness — an inner border. No border is drawn when borderThickness is 0 or unset.
  • cornerRadius — the box's corner radius.
  • iconColor — the caret icon colour. Falls back to the select's iconColor, then to theme.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 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, and borderThickness — fall back to the select's own values when unset.
  • boxShadow — a shadow around the dropdown, in the same shape as the select's boxShadow. No shadow when unset.
  • gap — space in pixels between the closed control and the dropdown. Defaults to 0, 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:

Loom Previewv0.13.2

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 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