R

ReflexKR

Reflex 官方文档

组件工坊:Reflex 组件库 100 例

·

课程概览

布局

·

Box

·

Flex

·

Stack

·

Grid

·

Container

·

Section

·

Center

·

Spacer

·

Separator

·

Card

·

Inset

·

Aspect Ratio

·

Fragment

排版

·

Text

·

Heading

·

Link

·

Code

·

Markdown

·

Blockquote

·

Quote

·

Strong

·

Em

·

Kbd

表单

·

Button

·

Input

·

Text Area

·

Checkbox

·

Switch

·

Radio Group

·

Select

·

Slider

·

Form

·

Upload

数据展示

·

Badge

·

Avatar

·

Icon

·

Callout

·

List

·

Data List

·

Code Block

·

Progress

·

Spinner

·

Scroll Area

·

Moment

展开与切换

·

Tabs

·

Accordion

·

Segmented Control

浮层

·

Tooltip

·

Popover

·

Hover Card

·

Dialog

·

Alert Dialog

·

Drawer

·

Dropdown Menu

·

Context Menu

·

Toast

动态渲染

·

Cond

·

Match

·

Foreach

·

Auto Scroll

媒体

·

Image

·

Audio

·

Video

表格与数据网格

·

Table

·

Data Table

·

Data Editor

图表

·

Area Chart

·

Bar Chart

·

Line Chart

·

Composed Chart

·

Pie Chart

·

Radar Chart

·

Radial Bar Chart

·

Scatter Chart

·

Funnel Chart

·

Treemap

·

Sankey Chart

·

Error Bar

图表部件

·

Axis

·

Cartesian Grid

·

Chart Tooltip

·

Legend

·

Label

·

Reference

·

Brush

其他图表库

·

Plotly

·

Pyplot

HTML 元素

·

HTML

·

HTML Text

·

HTML Layout

·

HTML Forms

·

HTML Media

·

HTML Tables

·

SVG

其他工具

·

Theme

·

Skeleton

·

Clipboard

·

Html Embed

·

Script

·

Memo

组件工坊:Reflex 组件库 100 例
表单

Select

rx.selectrx.select.rootrx.select.triggerrx.select.contentrx.select.grouprx.select.labelrx.select.itemrx.select.separator

官方文档

工坊订单

请在商品列表上方加一个分类下拉框。下方只显示所选分类的商品。

rx.select 是一个下拉框,点击后展开列表,从中选一个值。它基于 Radix UI,可以用键盘选择,在桌面和移动端表现一致。只传一个列表,就会同时生成按钮和列表。

  • 选项超过五个、需要节省纵向空间时,或者必须从预设列表中选一个时使用。
  • 选项少时,Radio Group 一眼就能看全,效果更好。需要多选就用一组 Checkbox,需要自己输入就用 Input。
  • 分组标题、分隔线、禁用某个选项这类精细的结构,用底层 API(rx.select.root …)来做。
rx.select(["apple", "grape", "pear"])

试着改属性

placeholder 只在什么都没选时显示。position="popper" 时列表在按钮下方展开;"item-aligned"(默认)时,列表展开后已选项会正好叠在按钮的位置上。

试着改属性

placeholder

variant

size

color

radius

position

high_contrast

disabled

rx.select(["apple", "grape", "pear"], placeholder="Choose a fruit...", variant="surface", size="2", color_scheme="indigo", radius="medium", position="item-aligned", high_contrast=False, disabled=False)
variant 还多一个连按钮边框都去掉的 ghost。high_contrast 会提高展开列表的对比度。
属性取值默认值用途
第一个参数(items)字符串列表 / Var必填选项列表,值与显示文字相同
value字符串 / Var无受控的选中值(配合 on_change)
default_value字符串无只在开始时选一次的值
placeholder字符串""选择前按钮上显示的提示文字
label字符串无展开列表顶部的分组标题
variant"classic" "surface" "soft" "ghost""surface"按钮外观
size"1" ~ "3""2"大小
color_scheme, radius, high_contrast主题值颜色、按钮圆角、列表对比度
position"item-aligned" "popper""item-aligned"列表的展开方式。在对话框里用 popper
widthCSS 长度无按钮宽度
open / default_openTrue / FalseFalse控制列表是否展开 / 一开始就展开
name, required, disabled表单提交名、必须选择才能提交、禁用
on_change事件选了别的选项时触发,传入值(字符串)
on_open_change事件列表展开、收起时触发,传入 True/False

跟随选中值

这是最常见的用法。把 State 变量接到 value,把修改这个变量的处理器接到 on_change。选中项始终显示 State 的值,所以无论在应用哪里改了值,下拉框都会跟着变。

You selected:

apple
class SelectFruitState(rx.State):
    selected_fruit: str = "apple"

    @rx.event
    def update_fruit(self, value: str):
        self.selected_fruit = value


rx.hstack(
    rx.select(
        ["apple", "grape", "pear"],
        value=SelectFruitState.selected_fruit,
        on_change=SelectFruitState.update_fruit,
    ),
    rx.text("You selected:"),
    rx.badge(SelectFruitState.selected_fruit),
    align="center",
    spacing="3",
)

初始值、提示文字、禁用

不需要 State、只想定初始选择时,用 default_value(非受控)。如果只在提交表单时才需要这个值,这样就够了。想以空状态开始、引导用户选择,就去掉 default_value,改给 placeholder。disabled=True 时会变灰,也无法展开。

rx.flex(
    rx.select(["apple", "grape", "pear"], default_value="grape"),
    rx.select(["apple", "grape", "pear"], placeholder="Choose a fruit..."),
    rx.select(["apple", "grape", "pear"], default_value="apple", disabled=True),
    spacing="3",
    wrap="wrap",
)

来自 State 的选项

选项通常来自数据库或 API。传入 State 里的列表 Var 而不是普通列表,列表变了,下拉选项也会变。内部用 rx.foreach 生成选项。

3 options

import random


class SelectDynamicState(rx.State):
    options: list[str] = ["apple", "grape", "pear"]
    selected: str = "apple"

    @rx.event
    def set_selected(self, value: str):
        self.selected = value

    @rx.event
    def randomize(self):
        self.selected = random.choice(self.options)

    @rx.event
    def add_option(self):
        new_options = ["banana", "orange", "mango", "kiwi", "cherry"]
        available = [o for o in new_options if o not in self.options]
        if available:
            self.options = self.options + [available[0]]

    @rx.event
    def reset_options(self):
        self.options = ["apple", "grape", "pear"]
        self.selected = "apple"


rx.vstack(
    rx.select(
        SelectDynamicState.options,
        value=SelectDynamicState.selected,
        on_change=SelectDynamicState.set_selected,
    ),
    rx.hstack(
        rx.button("Pick Random", on_click=SelectDynamicState.randomize, size="1"),
        rx.button("Add Option", on_click=SelectDynamicState.add_option, size="1", variant="soft"),
        rx.button("Reset", on_click=SelectDynamicState.reset_options, size="1", variant="soft", color_scheme="gray"),
    ),
    rx.text(SelectDynamicState.options.length(), " options", size="1", color_scheme="gray"),
)

在表单中使用

name 是提交数据的键,required=True 时选择之前无法提交。没有选择的 select 会以空值("")进入数据。

Favorite fruit

Quantity

Results:

{}
class SelectFormState(rx.State):
    form_data: dict = {}

    @rx.event
    def handle_submit(self, form_data: dict):
        self.form_data = form_data


rx.card(
    rx.vstack(
        rx.form.root(
            rx.vstack(
                rx.text("Favorite fruit", size="2"),
                rx.select(["apple", "grape", "pear"], name="fruit", placeholder="Pick one", required=True),
                rx.text("Quantity", size="2"),
                rx.select(["1", "2", "3", "4", "5"], name="quantity", default_value="1"),
                rx.button("Submit Order", type="submit"),
                spacing="2",
                align="stretch",
            ),
            on_submit=SelectFormState.handle_submit,
            reset_on_submit=True,
        ),
        rx.divider(),
        rx.hstack(rx.text("Results:", weight="bold"), rx.badge(SelectFormState.form_data.to_string())),
        spacing="3",
    ),
    width="320px",
)

分开显示名与值

高层 rx.select 的值与显示文字相同。如果要显示用户名、实际使用 ID,可以像官方文档那样,用计算变量从名字查出 ID。

Selected user ID: user_001

class SelectUserState(rx.State):
    users: dict[str, str] = {
        "user_001": "Alice Johnson",
        "user_002": "Bob Smith",
        "user_003": "Carol Davis",
    }
    selected_name: str = "Alice Johnson"

    @rx.var
    def user_names(self) -> list[str]:
        return list(self.users.values())

    @rx.var
    def selected_id(self) -> str:
        for uid, name in self.users.items():
            if name == self.selected_name:
                return uid
        return ""

    @rx.event
    def set_user(self, name: str):
        self.selected_name = name


rx.vstack(
    rx.select(
        SelectUserState.user_names,
        value=SelectUserState.selected_name,
        on_change=SelectUserState.set_user,
    ),
    rx.text("Selected user ID: ", rx.code(SelectUserState.selected_id), size="2"),
)

想直接在下拉框里分开,就用底层 API,写成 rx.select.item("显示文字", value="值")。on_change 传入的是 value。

value = kr

class SelectCountryState(rx.State):
    code: str = "kr"

    @rx.event
    def set_code(self, value: str):
        self.code = value


rx.hstack(
    rx.select.root(
        rx.select.trigger(),
        rx.select.content(
            rx.select.item("Korea", value="kr"),
            rx.select.item("Japan", value="jp"),
            rx.select.item("Germany", value="de"),
        ),
        value=SelectCountryState.code,
        on_change=SelectCountryState.set_code,
    ),
    rx.text("value = ", rx.code(SelectCountryState.code), size="2"),
    align="center",
)

在对话框中

在 Dialog 这类浮在页面上方的容器里,要给 position="popper"。这样列表才不会被对话框裁掉,而是在按钮正下方展开。

rx.dialog.root(
    rx.dialog.trigger(rx.button("Open Dialog")),
    rx.dialog.content(
        rx.dialog.title("Pick a fruit"),
        rx.vstack(
            rx.select(["apple", "grape", "pear"], position="popper", default_value="apple"),
            rx.dialog.close(rx.button("Close", variant="soft")),
            spacing="3",
        ),
        max_width="320px",
    ),
)

展开与收起时:on_open_change

on_open_change 在列表展开时传入 True,收起时传入 False。可以用来在展开时预先加载数据,或者统计次数。

Open count: 0

Closed
class SelectOpenState(rx.State):
    is_open: bool = False
    open_count: int = 0

    @rx.event
    def on_toggle(self, is_open: bool):
        self.is_open = is_open
        if is_open:
            self.open_count += 1


rx.vstack(
    rx.select(["apple", "grape", "pear"], default_value="apple", on_open_change=SelectOpenState.on_toggle),
    rx.hstack(
        rx.text("Open count: ", SelectOpenState.open_count, size="2"),
        rx.cond(
            SelectOpenState.is_open,
            rx.badge("Open", color_scheme="green"),
            rx.badge("Closed", color_scheme="gray"),
        ),
        align="center",
    ),
)

底层 API:子组件

高层 rx.select 只是替你把下面这些部件组装好。自己组装的话,就能加入分组标题和分隔线,或者只禁用某一个选项。

部件职责主要属性
rx.select.root整体与选中值value default_value on_change open default_open on_open_change name required disabled size
rx.select.trigger点击的按钮placeholder variant color_scheme radius
rx.select.content展开的列表position side side_offset align align_offset variant("solid" "soft") color_scheme high_contrast
rx.select.group选项分组
rx.select.label分组标题
rx.select.item单个选项value(必填) disabled
rx.select.separator分隔线

rx.select.content 还有 on_escape_key_down、on_pointer_down_outside、on_close_auto_focus 事件。下面是一个带两个分组、一条分隔线和一个禁用选项(Grape)的列表。

rx.select.root(
    rx.select.trigger(placeholder="Pick a food", variant="soft", radius="full"),
    rx.select.content(
        rx.select.group(
            rx.select.label("Fruits"),
            rx.select.item("Orange", value="orange"),
            rx.select.item("Apple", value="apple"),
            rx.select.item("Grape", value="grape", disabled=True),
        ),
        rx.select.separator(),
        rx.select.group(
            rx.select.label("Vegetables"),
            rx.select.item("Carrot", value="carrot"),
            rx.select.item("Potato", value="potato"),
        ),
        position="popper",
        side="bottom",
        side_offset=6,
        variant="soft",
        color_scheme="green",
    ),
    size="2",
)

完全受控:value 与 open

在底层 root 上,同样可以用 value + on_change 让 State 掌管选中项,用 open + on_open_change 掌管展开状态。可以用按钮切换选中项或展开列表。注意:如果 on_open_change 不去更新 open,点按钮时列表既不会展开也不会收起。

在表单中,root 的 name 是键,选中 item 的 value 是值。

class SelectControlState(rx.State):
    value: str = "apple"
    open: bool = False

    @rx.event
    def set_value(self, value: str):
        self.value = value

    @rx.event
    def set_open(self, value: bool):
        self.open = value

    @rx.event
    def next_fruit(self):
        fruits = ["apple", "grape", "pear"]
        self.value = fruits[(fruits.index(self.value) + 1) % 3]


rx.vstack(
    rx.select.root(
        rx.select.trigger(),
        rx.select.content(
            rx.select.item("apple", value="apple"),
            rx.select.item("grape", value="grape"),
            rx.select.item("pear", value="pear"),
        ),
        value=SelectControlState.value,
        on_change=SelectControlState.set_value,
        open=SelectControlState.open,
        on_open_change=SelectControlState.set_open,
    ),
    rx.hstack(
        rx.button("Next value", on_click=SelectControlState.next_fruit, size="1", variant="soft"),
        rx.button("Open menu", on_click=SelectControlState.set_open(True), size="1", variant="soft"),
    ),
)

联动选择

后一个 select 的选项随前一个 select 的值变化(地区 → 国家)。换地区时,处理器把国家也设成该地区的第一个国家,避免后一个 select 指向不存在的值。

class SelectCascadeState(rx.State):
    regions: dict[str, list[str]] = {
        "North America": ["USA", "Canada", "Mexico"],
        "Europe": ["UK", "France", "Germany"],
        "Asia": ["Japan", "Korea", "Singapore"],
    }
    region: str = "North America"
    country: str = "USA"

    @rx.event
    def set_region(self, value: str):
        self.region = value
        self.country = self.regions[value][0]

    @rx.event
    def set_country(self, value: str):
        self.country = value

    @rx.var
    def region_names(self) -> list[str]:
        return list(self.regions.keys())

    @rx.var
    def countries(self) -> list[str]:
        return self.regions.get(self.region, [])


rx.hstack(
    rx.select(SelectCascadeState.region_names, value=SelectCascadeState.region, on_change=SelectCascadeState.set_region),
    rx.select(SelectCascadeState.countries, value=SelectCascadeState.country, on_change=SelectCascadeState.set_country),
    spacing="3",
    wrap="wrap",
)

完成订单

这是用 select 决定下方显示哪些数据的筛选模式。计算变量 filtered_items 只返回所选分类的商品,rx.foreach 把它们排成卡片。label 显示在展开列表顶部,作为分组标题。

MacBook Pro
Dell XPS
class SelectFilterState(rx.State):
    all_items: list[dict[str, str]] = [
        {"name": "MacBook Pro", "category": "laptops"},
        {"name": "Dell XPS", "category": "laptops"},
        {"name": "iPhone 15", "category": "phones"},
        {"name": "Pixel 8", "category": "phones"},
        {"name": "iPad Air", "category": "tablets"},
        {"name": "Galaxy Tab", "category": "tablets"},
    ]
    category: str = "laptops"

    @rx.event
    def set_category(self, value: str):
        self.category = value

    @rx.var
    def filtered_items(self) -> list[dict[str, str]]:
        return [i for i in self.all_items if i["category"] == self.category]


rx.vstack(
    rx.select(
        ["laptops", "phones", "tablets"],
        value=SelectFilterState.category,
        on_change=SelectFilterState.set_category,
        label="Category",
    ),
    rx.foreach(SelectFilterState.filtered_items, lambda item: rx.card(item["name"], width="100%")),
    spacing="3",
    width="280px",
)

工坊笔记

  • 用 rx.select([选项...]) 创建,用 value + on_change 与 State 绑定。只定初始值用 default_value。

  • 选择前的提示用 placeholder,在对话框中用 position="popper"。

  • 把选项作为 State 列表传入,下拉列表就会跟随数据变化。

  • 显示文字与值不同,或需要分组·分隔线·禁用选项时,用 root · trigger · content · group · label · item · separator。

  • on_open_change 传入展开(True)/收起(False)。用 open 直接控制时,处理器必须更新 open。

← 上一课

表单 · Radio Group

下一课 →

表单 · Slider

订阅新课时和新课程通知

本课程新增课时或推出后续课程时,我们会发邮件通知你。

收集项目:电子邮箱 · 用途:本课程的新课时和上线通知 · 保存期限:直到你要求删除。不会用于其他用途,也不会提供给他人。回复任意通知邮件,我们会立即删除。

问答

关于本课有任何疑问都可以在这里留言。所有语言版本页面的提问会汇集在一起。

以纯文本显示,不支持链接和格式。

R

ReflexKR

动手学 Python 和 Reflex。所有示例均已在 Reflex 0.10.0 上实际运行验证。

ReflexKR 免费,没有横幅广告。如果对你有帮助,欢迎请我们喝杯咖啡。 ☕ 请我们喝杯咖啡

联盟链接与赞助说明
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex