El taller de componentes: los 100 componentes de Reflex
·
Resumen del curso
Diseño
·
Box
·
Flex
·
Stack
·
Grid
·
Container
·
Section
·
Center
·
Spacer
·
Separator
·
Card
·
Inset
·
Aspect Ratio
·
Fragment
Tipografía
·
Text
·
Heading
·
Link
·
Code
·
Markdown
·
Blockquote
·
Quote
·
Strong
·
Em
·
Kbd
Formularios
·
Button
·
Input
·
Text Area
·
Checkbox
·
Switch
·
Radio Group
·
Select
·
Slider
·
Form
·
Upload
Visualización de datos
·
Badge
·
Avatar
·
Icon
·
Callout
·
List
·
Data List
·
Code Block
·
Progress
·
Spinner
·
Scroll Area
·
Moment
Mostrar y ocultar
·
Tabs
·
Accordion
·
Segmented Control
Superposiciones
·
Tooltip
·
Popover
·
Hover Card
·
Dialog
·
Alert Dialog
·
Drawer
·
Dropdown Menu
·
Context Menu
·
Toast
Renderizado dinámico
·
Cond
·
Match
·
Foreach
·
Auto Scroll
Multimedia
·
Image
·
Audio
·
Video
Tablas y cuadrículas de datos
·
Table
·
Data Table
·
Data Editor
Gráficos
·
Area Chart
·
Bar Chart
·
Line Chart
·
Composed Chart
·
Pie Chart
·
Radar Chart
·
Radial Bar Chart
·
Scatter Chart
·
Funnel Chart
·
Treemap
·
Sankey Chart
·
Error Bar
Piezas de gráficos
·
Axis
·
Cartesian Grid
·
Chart Tooltip
·
Legend
·
Label
·
Reference
·
Brush
Otras bibliotecas de gráficos
·
Plotly
·
Pyplot
Elementos HTML
·
HTML
·
HTML Text
·
HTML Layout
·
HTML Forms
·
HTML Media
·
HTML Tables
·
SVG
Otras herramientas
·
Theme
·
Skeleton
·
Clipboard
·
Html Embed
·
Script
·
Memo
rx.selectrx.select.rootrx.select.triggerrx.select.contentrx.select.grouprx.select.labelrx.select.itemrx.select.separatorDocumentación oficial
Pedido del taller
Pon un desplegable de categorías encima de la lista de productos. Debajo solo deben aparecer los productos de la categoría elegida.
rx.select es un desplegable para elegir un valor de una lista que se abre al pulsarlo. Está construido sobre Radix UI, así que también se puede usar con el teclado y funciona igual en escritorio y en móvil. Basta con pasarle una lista para que cree el botón y la lista a la vez.
rx.select.root …).rx.select(["apple", "grape", "pear"])placeholder solo se ve cuando no se ha elegido nada. Con position="popper" la lista se abre debajo del botón; con "item-aligned" (por defecto), se abre de modo que la opción elegida quede encima del botón.
Prueba las propiedades
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)| Propiedad | Valores | Por defecto | Uso |
|---|---|---|---|
Primer argumento (items) | Lista de cadenas / Var | Obligatorio | Lista de opciones. Valor y texto visible coinciden |
value | Cadena / Var | Ninguno | Selección controlada (junto con on_change) |
default_value | Cadena | Ninguno | Valor elegido solo al principio |
placeholder | Cadena | "" | Texto de ayuda en el botón antes de elegir |
label | Cadena | Ninguno | Título de grupo en lo alto de la lista desplegada |
variant | "classic" "surface" "soft" "ghost" | "surface" | Aspecto del botón |
size | "1" ~ "3" | "2" | Tamaño |
color_scheme, radius, high_contrast | Valor del tema | Color, esquinas del botón, contraste de la lista | |
position | "item-aligned" "popper" | "item-aligned" | Cómo se abre la lista. Dentro de un diálogo, popper |
width | Longitud CSS | Ninguno | Ancho del botón |
open / default_open | True / False | False | Controlar si la lista está abierta / abrirla desde el principio |
name, required, disabled | Nombre para el envío, hay que elegir para enviar, bloquear | ||
on_change | Evento | Al elegir otra opción. Pasa el valor (cadena) | |
on_open_change | Evento | Al abrirse y cerrarse la lista. Pasa True/False |
Es el uso más habitual. Conecta a value una variable del State y a on_change un handler que la actualice. La selección muestra siempre el valor del State, así que el desplegable cambia aunque el valor se modifique desde cualquier parte de la app.
You selected:
appleclass 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",
)Si no necesitas State y solo quieres fijar la selección inicial, usa default_value (no controlado). Si el valor solo hace falta al enviar el formulario, con esto basta. Para empezar vacío e invitar a elegir, quita default_value y pon placeholder. Con disabled=True se ve atenuado y no se abre.
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",
)Normalmente las opciones vienen de una base de datos o de una API. Si en lugar de una lista pasas un Var de lista del State, la lista de opciones cambia cuando cambia esa lista. Internamente, las opciones se crean con 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 es la clave de los datos enviados y, con required=True, no se envía hasta que se elige algo. Un select sin elegir entra como valor vacío ("").
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",
)En el rx.select de alto nivel, el valor y el texto visible coinciden. Si quieres mostrar nombres de usuario pero usar su ID, puedes buscar el ID a partir del nombre con una variable calculada, como hace la documentación oficial.
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"),
)Para separarlos dentro del propio desplegable, usa la API de bajo nivel: rx.select.item("texto visible", value="valor"). on_change pasa el 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",
)Dentro de un contenedor que flota sobre la pantalla, como Dialog, pon position="popper". Así la lista no queda cortada por el diálogo y se abre justo debajo del botón.
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 pasa True cuando la lista se abre y False cuando se cierra. Sirve para precargar datos al abrirla o para contar cuántas veces se abre.
Open count: 0
Closedclass 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",
),
)El rx.select de alto nivel no es más que un montaje automático de las piezas de abajo. Si lo montas tú, puedes añadir títulos de grupo y separadores, y bloquear una sola opción.
| Pieza | Función | Propiedades principales |
|---|---|---|
rx.select.root | El conjunto y la selección | value default_value on_change open default_open on_open_change name required disabled size |
rx.select.trigger | El botón que se pulsa | placeholder variant color_scheme radius |
rx.select.content | La lista desplegada | position side side_offset align align_offset variant ("solid" "soft") color_scheme high_contrast |
rx.select.group | Grupo de opciones | |
rx.select.label | Título del grupo | |
rx.select.item | Una opción | value (obligatorio) disabled |
rx.select.separator | Separador |
rx.select.content también tiene los eventos on_escape_key_down, on_pointer_down_outside y on_close_auto_focus. Abajo hay una lista con dos grupos, un separador y una opción bloqueada (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",
)También con el root de bajo nivel, el State puede controlar la selección con value + on_change y la apertura con open + on_open_change. Así se puede cambiar la selección o abrir la lista desde un botón. Ojo: si on_open_change no actualiza open, la lista no se abrirá ni se cerrará aunque pulses el botón.
En un formulario, el name del root es la clave y el value del item elegido es el valor.
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"),
),
)Es el patrón en el que las opciones del segundo select dependen del valor del primero (región → país). Al cambiar la región, el handler ajusta también el país al primero de esa región, para que el segundo select nunca apunte a un valor que no existe.
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",
)Es el patrón de filtro, en el que el select decide qué datos se muestran debajo. La variable calculada filtered_items devuelve solo los productos de la categoría elegida y rx.foreach los coloca como tarjetas. label aparece como título de grupo en lo alto de la lista desplegada.
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",
)Notas del taller
Se crea con rx.select([opciones...]) y se une al State con value + on_change. Solo el valor inicial, default_value.
Texto de ayuda antes de elegir: placeholder; dentro de un diálogo, position="popper".
Si pasas las opciones como lista del State, la lista sigue a los datos.
Si el texto visible y el valor difieren, o necesitas grupos, separadores u opciones bloqueadas: root · trigger · content · group · label · item · separator.
on_open_change pasa abierto (True)/cerrado (False). Si controlas open tú mismo, el handler debe actualizar open.
Recibe avisos de nuevas lecciones
Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.
R
ReflexKR
Aprende Python y Reflex haciendo. Todos los ejemplos se ejecutaron y verificaron en Reflex 0.10.0.
ReflexKR es gratis y sin banners publicitarios. Si te ayudó, puedes apoyarlo con un café. ☕ Invítanos un café
Aviso de afiliados© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Preguntas y respuestas
Pregunta lo que quieras sobre esta lección. Aquí se reúnen las preguntas de todas las versiones de idioma de esta página.