R

ReflexKR

Reflex-Dokumentation

Die Komponentenwerkstatt: Reflex' 100 Bibliotheksbausteine

·

Kursüberblick

Layout

·

Box

·

Flex

·

Stack

·

Grid

·

Container

·

Section

·

Center

·

Spacer

·

Separator

·

Card

·

Inset

·

Aspect Ratio

·

Fragment

Typografie

·

Text

·

Heading

·

Link

·

Code

·

Markdown

·

Blockquote

·

Quote

·

Strong

·

Em

·

Kbd

Formulare

·

Button

·

Input

·

Text Area

·

Checkbox

·

Switch

·

Radio Group

·

Select

·

Slider

·

Form

·

Upload

Datenanzeige

·

Badge

·

Avatar

·

Icon

·

Callout

·

List

·

Data List

·

Code Block

·

Progress

·

Spinner

·

Scroll Area

·

Moment

Auf- und Zuklappen

·

Tabs

·

Accordion

·

Segmented Control

Overlays

·

Tooltip

·

Popover

·

Hover Card

·

Dialog

·

Alert Dialog

·

Drawer

·

Dropdown Menu

·

Context Menu

·

Toast

Dynamisches Rendern

·

Cond

·

Match

·

Foreach

·

Auto Scroll

Medien

·

Image

·

Audio

·

Video

Tabellen und Datengrids

·

Table

·

Data Table

·

Data Editor

Diagramme

·

Area Chart

·

Bar Chart

·

Line Chart

·

Composed Chart

·

Pie Chart

·

Radar Chart

·

Radial Bar Chart

·

Scatter Chart

·

Funnel Chart

·

Treemap

·

Sankey Chart

·

Error Bar

Diagrammbausteine

·

Axis

·

Cartesian Grid

·

Chart Tooltip

·

Legend

·

Label

·

Reference

·

Brush

Andere Diagrammbibliotheken

·

Plotly

·

Pyplot

HTML-Elemente

·

HTML

·

HTML Text

·

HTML Layout

·

HTML Forms

·

HTML Media

·

HTML Tables

·

SVG

Weitere Werkzeuge

·

Theme

·

Skeleton

·

Clipboard

·

Html Embed

·

Script

·

Memo

Die Komponentenwerkstatt: Reflex' 100 Bibliotheksbausteine
Formulare

Select

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

Offizielle Doku

Werkstattauftrag

Bitte setz über die Produktliste ein Kategorie-Dropdown. Darunter sollen nur die Produkte der gewählten Kategorie erscheinen.

rx.select ist ein Dropdown, bei dem man aus einer aufklappenden Liste einen Wert wählt. Es baut auf Radix UI auf, lässt sich auch per Tastatur bedienen und verhält sich auf Desktop und Mobilgeräten gleich. Übergibst du nur eine Liste, entstehen Button und Liste zusammen.

  • Nimm es, wenn es mehr als fünf Optionen gibt und vertikaler Platz knapp ist, oder wenn aus einer festen Liste genau eine Option gewählt werden muss.
  • Bei wenigen Optionen ist Radio Group besser, weil alles auf einen Blick sichtbar ist. Für Mehrfachauswahl nimm eine Gruppe von Checkboxen, für freie Eingabe Input.
  • Feinere Aufbauten wie Gruppentitel, Trennlinien oder einzelne gesperrte Optionen baust du mit der Low-Level-API (rx.select.root …).
rx.select(["apple", "grape", "pear"])

Props ausprobieren

placeholder erscheint nur, solange nichts gewählt ist. Mit position="popper" klappt die Liste unter dem Button auf, mit "item-aligned" (Standard) so, dass die gewählte Option über dem Button liegt.

Props ausprobieren

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 hat zusätzlich ghost, das sogar den Rahmen des Buttons entfernt. high_contrast erhöht den Kontrast der aufgeklappten Liste.
PropWerteStandardVerwendung
Erstes Argument (items)Liste von Strings / VarerforderlichOptionen. Wert und angezeigter Text sind gleich
valueString / VarkeinerGesteuerte Auswahl (zusammen mit on_change)
default_valueStringkeinerAuswahl, die nur einmal am Anfang gesetzt wird
placeholderString""Hinweistext auf dem Button vor der Auswahl
labelStringkeinerGruppentitel ganz oben in der aufgeklappten Liste
variant"classic" "surface" "soft" "ghost""surface"Aussehen des Buttons
size"1" bis "3""2"Größe
color_scheme, radius, high_contrastTheme-WertFarbe, Ecken des Buttons, Kontrast der Liste
position"item-aligned" "popper""item-aligned"Wie die Liste aufklappt. In Dialogen popper
widthCSS-LängekeinerBreite des Buttons
open / default_openTrue / FalseFalseOffenen Zustand steuern / von Anfang an offen
name, required, disabledName beim Absenden, Auswahl zum Absenden nötig, Sperren
on_changeEventWenn eine andere Option gewählt wird. Übergibt den Wert (String)
on_open_changeEventWenn die Liste auf- oder zuklappt. Übergibt True/False

Der gewählte Wert im State

Der häufigste Fall: Du verbindest value mit einer State-Variable und on_change mit einem Handler, der diese Variable ändert. Die Auswahl zeigt immer den State-Wert, das Dropdown zieht also mit, egal wo in der App der Wert geändert wird.

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",
)

Anfangswert, Hinweistext, sperren

Brauchst du keinen State und willst nur die Anfangsauswahl festlegen, nimm default_value (ungesteuert). Brauchst du den Wert nur beim Absenden eines Formulars, reicht das völlig. Soll es leer starten und zur Auswahl auffordern, lass default_value weg und setz placeholder. Mit disabled=True ist es abgeblendet und klappt nicht auf.

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",
)

Optionen aus dem State

Optionen kommen meist aus einer Datenbank oder API. Übergibst du statt einer Liste eine Listen-Var aus dem State, ändert sich die Liste mit, wenn sich die Daten ändern. Intern werden die Optionen mit rx.foreach erzeugt.

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"),
)

Im Formular

name wird zum Schlüssel der gesendeten Daten, und mit required=True wird erst nach einer Auswahl abgeschickt. Ein Select ohne Auswahl kommt als leerer Wert ("") an.

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",
)

Angezeigten Namen und Wert trennen

Beim High-Level-rx.select sind Wert und angezeigter Text gleich. Willst du Benutzernamen zeigen, aber mit IDs arbeiten, kannst du wie in der offiziellen Doku per berechneter Variable die ID zum Namen suchen.

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"),
)

Um direkt im Dropdown zu trennen, schreibst du mit der Low-Level-API rx.select.item("Angezeigter Text", value="Wert"). on_change übergibt dann 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",
)

In einem Dialog

In schwebenden Containern wie Dialog gibst du position="popper" an. Nur dann klappt die Liste direkt unter dem Button auf, ohne vom Dialog abgeschnitten zu werden.

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",
    ),
)

Beim Auf- und Zuklappen: on_open_change

on_open_change übergibt beim Aufklappen True und beim Zuklappen False. Damit lädst du beim Öffnen Daten vor oder zählst mit.

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",
    ),
)

Low-Level-API: Unterkomponenten

Das High-Level-rx.select setzt nur die folgenden Bausteine für dich zusammen. Baust du sie selbst zusammen, kannst du Gruppentitel und Trennlinien einfügen und einzelne Optionen sperren.

BausteinAufgabeWichtige Props
rx.select.rootGanzes und Auswahlvalue default_value on_change open default_open on_open_change name required disabled size
rx.select.triggerDer Button zum Klickenplaceholder variant color_scheme radius
rx.select.contentDie aufgeklappte Listeposition side side_offset align align_offset variant("solid" "soft") color_scheme high_contrast
rx.select.groupGruppe von Optionen
rx.select.labelGruppentitel
rx.select.itemEine Optionvalue (erforderlich) disabled
rx.select.separatorTrennlinie

rx.select.content hat außerdem die Events on_escape_key_down, on_pointer_down_outside und on_close_auto_focus. Unten eine Liste mit zwei Gruppen, einer Trennlinie und einer gesperrten Option (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",
)

Vollständig steuern: value und open

Auch beim Low-Level-root kann der State per value + on_change die Auswahl und per open + on_open_change den offenen Zustand halten. So lässt sich die Auswahl per Button ändern oder die Liste öffnen. Achtung: Ändert on_open_change nicht open, klappt die Liste beim Klicken weder auf noch zu.

Im Formular ist name des root der Schlüssel und value des gewählten item der Wert.

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"),
    ),
)

Verkettete Auswahl

Ein Muster, bei dem die Optionen des zweiten Selects vom Wert des ersten abhängen (Region → Land). Ändert sich die Region, setzt der Handler das Land auf das erste Land dieser Region, damit das zweite Select nie auf einen nicht vorhandenen Wert zeigt.

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",
)

Auftrag erledigt

Ein Filtermuster: Das Select bestimmt, welche Daten darunter erscheinen. Die berechnete Variable filtered_items liefert nur die Produkte der gewählten Kategorie, und rx.foreach reiht sie als Karten auf. label erscheint als Gruppentitel ganz oben in der aufgeklappten Liste.

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",
)

Werkstattnotizen

  • Mit rx.select([Optionen...]) anlegen und per value + on_change an den State binden. Nur Anfangswert: default_value.

  • Hinweis vor der Auswahl per placeholder, in Dialogen position="popper".

  • Übergibst du die Optionen als State-Liste, folgt die Liste den Daten.

  • Weichen Text und Wert ab oder brauchst du Gruppen · Trennlinien · gesperrte Optionen: root · trigger · content · group · label · item · separator.

  • on_open_change meldet offen (True)/geschlossen (False). Steuerst du open selbst, muss der Handler open ändern.

← Zurück

Formulare · Radio Group

Weiter →

Formulare · Slider

Über neue Lektionen informiert werden

Wir schicken dir eine E-Mail, wenn dieser Kurs neue Lektionen bekommt oder ein Folgekurs erscheint.

Erhoben: deine E-Mail-Adresse · Zweck: Hinweise auf neue Lektionen oder den Start dieses Kurses · Speicherdauer: bis du die Löschung verlangst. Keine andere Nutzung, keine Weitergabe. Antworte einfach auf eine Benachrichtigung, dann löschen wir sie sofort.

Fragen & Antworten

Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.

Wird als reiner Text angezeigt, ohne Links oder Formatierung.

R

ReflexKR

Python und Reflex zum Anfassen. Alle Beispiele wurden mit Reflex 0.10.0 ausgeführt und geprüft.

ReflexKR ist kostenlos und ohne Werbebanner. Wenn es dir geholfen hat, kannst du es mit einem Kaffee unterstützen. ☕ Spendier uns einen Kaffee

Hinweis zu Affiliate-Links
한국어English日本語简体中文EspañolDeutsch

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

Built with Reflex