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

Upload

rx.uploadrx.upload.root

Offizielle Doku

Werkstattauftrag

Bitte baue auf der Erstattungsseite ein Feld zum Anhängen von Belegfotos. Bis zu 3 Bilder; per Drag & Drop sofort angehängt, mit Name und Größe.

rx.upload ist ein Bereich (Dropzone), in den man Dateien zieht oder per Klick auswählt. Ausgewählte Dateien bleiben zunächst im Browser; erst wenn du einem Event-Handler rx.upload_files() übergibst, gehen sie an den Server. Der Handler auf dem Server bekommt die Dateiliste und liest oder speichert sie.

Die Beispiele dieses Kurses laufen auf einem gemeinsamen Server, daher speichern sie keine Dateien und zeigen nur Name und Größe. Code zum Speichern wird separat nur als Code gezeigt.

rx.upload(
    rx.text("Drop files here or click to select"),
    id="upload_basic",
    border="2px dashed var(--gray-7)",
    padding="2em",
)

Props ausprobieren

Diese Props bestimmen das Verhalten der Dropzone. Wähl ein paar Dateien aus und schalte dann multiple aus oder no_click ein. Die Namen der gewählten Dateien erscheinen unten als Badges.

Props ausprobieren

label

multiple

disabled

no_click

no_drag

no_keyboard

rx.vstack(rx.upload(rx.text("Drop files here or click to select"), id="upload_lab", multiple=True, disabled=False, no_click=False, no_drag=False, no_keyboard=False, border="2px dashed var(--gray-7)", padding="2em"), rx.foreach(rx.selected_files("upload_lab"), lambda f: rx.badge(f, variant="soft")))
no_click verhindert die Auswahl per Klick, no_drag das Hineinziehen, no_keyboard das Öffnen mit Leertaste · Enter. In 0.10 ist multiple standardmäßig True.
PropWerteStandardVerwendung
idString (fest)"default"Name dieser Dropzone. Die speziellen Vars · Events unten nutzen dieselbe id
accept{MIME: [Endungen...]}alle DateienErlaubte Dateitypen
multipleTrue / FalseTrueMehrfachauswahl
max_filesGanzzahlunbegrenztHöchstzahl an Dateien
max_size, min_sizeBytesunbegrenztMaximal- · Mindestgröße einer Datei
disabledTrue / FalseFalseDropzone abschalten
no_click, no_drag, no_keyboardTrue / FalseFalseKlicken / Hineinziehen / Öffnen per Tastatur verhindern
drag_active_styleStyle-dictkeinerStil, während eine Datei hineingezogen wird (0.10)
on_dropEventmerkt sich die AuswahlLäuft sofort, wenn Dateien abgelegt oder gewählt werden
on_drop_rejectedEventFehler-ToastWenn Dateien die Bedingungen nicht erfüllen

Gewählte Dateien anzeigen: selected_files

Gewählte Dateien landen in einer Liste im Browser. Die spezielle Var rx.selected_files(id) zeigt deren Namen an, das spezielle Event rx.clear_selected_files(id) leert sie. An den Server ist noch nichts gegangen.

id ist der Schlüssel, der Dropzone und spezielle Vars · Events verbindet, und muss überall gleich sein. Schreib sie als String oder Modulkonstante; State-Variablen gehen nicht. Der Button „Select File“ in der Dropzone ist nur Optik, denn jeder Klick in die Dropzone öffnet das Dateiauswahlfenster.

rx.vstack(
    rx.upload(
        rx.vstack(
            rx.button("Select File", variant="outline"),
            rx.text("Drag and drop files here or click to select files", size="2"),
            align="center",
        ),
        id="upload_selected",
        border="1px dotted var(--indigo-8)",
        padding="2em",
    ),
    rx.hstack(rx.foreach(rx.selected_files("upload_selected"), lambda f: rx.badge(f)), wrap="wrap"),
    rx.button("Clear", on_click=rx.clear_selected_files("upload_selected"), variant="soft", color_scheme="gray"),
)

An den Server senden: rx.upload_files

Übergibst du dem Handler rx.upload_files(upload_id=id), gehen die gewählten Dateien an den Server. Der Handler ist ein ganz normales async-Event, das ein einziges files: list[rx.UploadFile] bekommt. Pro Datei stehen file.name (Originalname), file.size (Bytes) und await file.read() (Inhalt) zur Verfügung.

Upload-Handler bekommen kein @rx.event(background=True) und nutzen auch kein async with self; das gilt nur für den Chunk-Upload weiter unten. Und den Upload-Button setzt du außerhalb der Dropzone. Liegt er darin, öffnet jeder Klick das Dateifenster und startet zugleich den Upload.

def upload_size_label(size: int) -> str:
    return f"{size / 1024:.1f} KB" if size < 1024 * 1024 else f"{size / 1024 / 1024:.2f} MB"


class UploadInfoState(rx.State):
    files: list[dict[str, str]] = []

    @rx.event
    async def handle_upload(self, files: list[rx.UploadFile]):
        # Read only the name and size. Nothing is saved to disk.
        self.files = [{"name": str(f.name), "size": upload_size_label(f.size or 0)} for f in files]


rx.vstack(
    rx.upload(
        rx.text("Drop up to 5 files (2 MB each)"),
        id="upload_info",
        max_files=5,
        max_size=2 * 1024 * 1024,
        border="2px dashed var(--gray-7)",
        padding="2em",
    ),
    rx.hstack(rx.foreach(rx.selected_files("upload_info"), lambda f: rx.badge(f, variant="soft")), wrap="wrap"),
    rx.hstack(
        rx.button("Upload", on_click=UploadInfoState.handle_upload(rx.upload_files(upload_id="upload_info"))),
        rx.button("Clear", on_click=rx.clear_selected_files("upload_info"), variant="soft", color_scheme="gray"),
    ),
    rx.foreach(
        UploadInfoState.files,
        lambda f: rx.hstack(rx.icon("file", size=14), rx.text(f["name"], size="2"), rx.code(f["size"]), align="center"),
    ),
)

Speichern und anzeigen (nur als Code)

In einer echten App speicherst du in den Ordner, den rx.get_upload_dir() zurückgibt (Path, standardmäßig ./uploaded_files, änderbar über die Umgebungsvariable REFLEX_UPLOADED_FILES_DIR), und in der Oberfläche baust du mit rx.get_upload_url(Dateiname) die Adresse für rx.image usw. Unten das Grundbeispiel der offiziellen Doku; auf dieser Seite wird es nicht ausgeführt.

class State(rx.State):
    uploaded_files: list[str] = []

    @rx.event
    async def handle_upload(self, files: list[rx.UploadFile]):
        for file in files:
            data = await file.read()
            path = rx.get_upload_dir() / file.name
            with path.open("wb") as f:
                f.write(data)
            self.uploaded_files.append(file.name)


def upload_component():
    return rx.vstack(
        rx.upload(id="upload"),
        rx.button("Upload", on_click=State.handle_upload(rx.upload_files("upload"))),
        rx.foreach(
            State.uploaded_files,
            lambda f: rx.image(src=rx.get_upload_url(f), alt="Uploaded file preview"),
        ),
    )
  • rx.get_upload_dir(): Speicherpfad auf dem Server (Backend). Wird im Event-Handler verwendet.
  • rx.get_upload_url(name): Adresse für die Oberfläche (Frontend). Kein Python-String, sondern eine Frontend-Var, also nur im Komponentencode verwenden. Die Adresse hat die Form /_upload/Dateiname, aber schreib sie nicht selbst, sondern geh über diese Funktion.
  • Der Upload-Ordner ist unter /_upload für alle öffentlich. Speichere am besten mit zufälligem Präfix, damit sich gleiche Namen nicht überschreiben.
  • Bei Hostern wie Reflex Cloud wird der Upload-Ordner bei jedem Deployment geleert. Dateien, die bleiben sollen, gehören in einen externen Speicher wie S3.

Sofort hochladen: on_drop und Filter

Verbindest du on_drop mit einem Handler, wird ohne Button sofort bei der Auswahl hochgeladen. accept filtert den Dateityp, max_files und max_size Anzahl und Größe. Dateien, die die Bedingungen nicht erfüllen, gehen an on_drop_rejected; ohne dieses Event zeigt 0.10 einen Fehler-Toast. Leg unten eine Datei ab, die kein Bild und kein PDF ist, oder eine über 2MB.

class UploadDropState(rx.State):
    names: list[str] = []
    rejected: int = 0

    @rx.event
    async def handle_drop(self, files: list[rx.UploadFile]):
        self.names = [f"{f.name} ({f.size or 0} bytes)" for f in files]

    @rx.event
    def handle_rejected(self, rejections: list):
        self.rejected = len(rejections)


rx.vstack(
    rx.upload(
        rx.text("Images or PDF only, up to 3 files", size="2"),
        id="upload_drop",
        multiple=True,
        accept={
            "application/pdf": [".pdf"],
            "image/png": [".png"],
            "image/jpeg": [".jpg", ".jpeg"],
            "image/webp": [".webp"],
        },
        max_files=3,
        max_size=2 * 1024 * 1024,
        no_keyboard=True,
        on_drop=UploadDropState.handle_drop(rx.upload_files(upload_id="upload_drop")),
        on_drop_rejected=UploadDropState.handle_rejected,
        border="2px dashed var(--gray-7)",
        padding="2em",
    ),
    rx.foreach(UploadDropState.names, lambda n: rx.text(n, size="2")),
    rx.cond(UploadDropState.rejected > 0, rx.text("Rejected: ", UploadDropState.rejected, size="2", color_scheme="red")),
)

Ungestylte Dropzone: rx.upload.root

rx.upload ist eine Dropzone mit Standardrahmen und -abstand. Willst du das Aussehen komplett selbst bauen, nimm rx.upload.root. Props und Events sind dieselben. Mit drag_active_style änderst du den Hintergrund, solange eine Datei hineingezogen wird.

rx.vstack(
    rx.upload.root(
        rx.box(
            rx.icon("cloud-upload", size=40, color=rx.color("indigo", 9)),
            rx.hstack(rx.text("Click to upload", weight="bold", color=rx.color("indigo", 11)), rx.text("or drag and drop"), size="2"),
            rx.text("SVG, PNG, JPG or GIF (MAX. 5MB)", size="1", color=rx.color("gray", 10)),
            display="flex",
            flex_direction="column",
            align_items="center",
            gap="6px",
            padding="1.5rem",
            text_align="center",
        ),
        id="upload_root_demo",
        max_width="24rem",
        border=f"2px dashed {rx.color('indigo', 7)}",
        border_radius="12px",
        cursor="pointer",
        drag_active_style={"background_color": rx.color("indigo", 3)},
    ),
    rx.hstack(rx.foreach(rx.selected_files("upload_root_demo"), lambda f: rx.badge(f)), wrap="wrap"),
)

Fortschritt und Abbrechen

rx.upload_files (und rx.upload_files_chunk) nehmen on_upload_progress. Während des Uploads kommt bei diesem Handler ein dict {"loaded": gesendete Bytes, "total": Gesamtbytes, "progress": 0–1} an. rx.cancel_upload(id) stoppt einen laufenden Upload. Diese Seite nimmt nur Dateien bis 2MB, daher ist der Fortschritt schnell bei 100.

Total bytes received: 0

class UploadProgressState(rx.State):
    uploading: bool = False
    progress: int = 0
    total_bytes: int = 0

    @rx.event
    async def handle_upload(self, files: list[rx.UploadFile]):
        self.total_bytes = sum(f.size or 0 for f in files)

    @rx.event
    def handle_upload_progress(self, progress: dict):
        self.uploading = True
        self.progress = round(progress["progress"] * 100)
        if self.progress >= 100:
            self.uploading = False

    @rx.event
    def cancel_upload(self):
        self.uploading = False
        return rx.cancel_upload("upload_progress")


rx.vstack(
    rx.upload(
        rx.text("Drag and drop files here or click to select files", size="2"),
        id="upload_progress",
        max_size=2 * 1024 * 1024,
        border="1px dotted var(--indigo-8)",
        padding="2em",
    ),
    rx.vstack(rx.foreach(rx.selected_files("upload_progress"), lambda f: rx.text(f, size="2"))),
    rx.progress(value=UploadProgressState.progress, max=100),
    rx.cond(
        ~UploadProgressState.uploading,
        rx.button(
            "Upload",
            on_click=UploadProgressState.handle_upload(
                rx.upload_files(
                    upload_id="upload_progress",
                    on_upload_progress=UploadProgressState.handle_upload_progress,
                ),
            ),
        ),
        rx.button("Cancel", on_click=UploadProgressState.cancel_upload, color_scheme="red"),
    ),
    rx.text("Total bytes received: ", UploadProgressState.total_bytes, size="2"),
    width="100%",
)

Große Dateien: Chunk-Upload (nur als Code)

await file.read() lädt die ganze Datei in den Speicher. Große Dateien nimmst du mit rx.upload_files_chunk(upload_id=...) stückweise entgegen und schreibst sie direkt auf die Festplatte. Der Chunk-Handler muss @rx.event(background=True) sein, bekommt chunk_iter: rx.UploadChunkIterator und muss ihn bis zum Ende lesen (wer vorher zurückkehrt, lässt den Upload scheitern). Jedes Stück hat chunk.filename, chunk.offset, chunk.content_type und chunk.data. Weil der Code auf die Festplatte schreibt, wird er hier nicht ausgeführt.

class ChunkUploadState(rx.State):
    uploaded_files: list[str] = []

    @rx.event(background=True)
    async def handle_large_upload(self, chunk_iter: rx.UploadChunkIterator):
        file_handles = {}
        try:
            async for chunk in chunk_iter:  # must consume every chunk
                fh = file_handles.get(chunk.filename)
                if fh is None:
                    path = rx.get_upload_dir() / "stream" / chunk.filename
                    path.parent.mkdir(parents=True, exist_ok=True)
                    fh = file_handles[chunk.filename] = path.open("wb")
                fh.seek(chunk.offset)
                fh.write(chunk.data)
        finally:
            for fh in file_handles.values():
                fh.close()
        async with self:
            self.uploaded_files = sorted(file_handles)


# rx.button("Upload Large Files",
#           on_click=ChunkUploadState.handle_large_upload(rx.upload_files_chunk(upload_id="large_upload")))

Upload im Formular

Dateien landen nicht im on_submit des Formulars. Steckt eine Dropzone im Formular, schickst du die Dateien separat über einen Button mit type="button" oder per on_drop mit rx.upload_files() und nimmst nur die übrigen Felder per on_submit entgegen.

Auftrag erledigt

Die Dropzone filtert vorab Bildtyp, Anzahl und Größe, und dank on_drop bekommt der Server Namen und Größe, sobald abgelegt wird. Die Liste behält nur die letzten drei. Gespeichert wird nichts.

Receipt photos (max 3, 2 MB each)

class UploadReceiptState(rx.State):
    files: list[dict[str, str]] = []

    @rx.event
    async def attach(self, files: list[rx.UploadFile]):
        added = [{"name": str(f.name), "size": f"{(f.size or 0) / 1024:.0f} KB"} for f in files]
        self.files = (self.files + added)[-3:]

    @rx.event
    def clear(self):
        self.files = []


rx.card(
    rx.vstack(
        rx.text("Receipt photos (max 3, 2 MB each)", weight="bold", size="2"),
        rx.upload(
            rx.hstack(rx.icon("image-plus", size=18), rx.text("Drop or click to attach", size="2"), align="center"),
            id="upload_receipt",
            accept={"image/png": [".png"], "image/jpeg": [".jpg", ".jpeg"]},
            max_files=3,
            max_size=2 * 1024 * 1024,
            on_drop=UploadReceiptState.attach(rx.upload_files(upload_id="upload_receipt")),
            border="1px dashed var(--gray-8)",
            border_radius="8px",
            padding="1em",
            width="100%",
            drag_active_style={"background_color": "var(--accent-3)"},
        ),
        rx.foreach(
            UploadReceiptState.files,
            lambda f: rx.hstack(rx.icon("image", size=14), rx.text(f["name"], size="2"), rx.spacer(), rx.code(f["size"]), width="100%", align="center"),
        ),
        rx.button("Remove all", on_click=UploadReceiptState.clear, size="1", variant="soft", color_scheme="gray"),
        width="100%",
    ),
    width="100%",
    max_width="380px",
)

Werkstattnotizen

  • Dropzone per rx.upload(id="feste-id"). Gewählte Dateien über rx.selected_files(id), leeren mit rx.clear_selected_files(id).

  • Senden über den Handler (rx.upload_files(upload_id=id)). Der Handler ist async, files: list[rx.UploadFile].

  • Mit on_drop wird sofort bei der Auswahl hochgeladen. Filter über accept · max_files · max_size, Abgelehntes an on_drop_rejected.

  • Speichern mit rx.get_upload_dir(), anzeigen mit rx.get_upload_url(). Der Upload-Ordner ist öffentlich.

  • Upload-Button außerhalb der Dropzone. Große Dateien mit rx.upload_files_chunk + Background-Handler.

← Zurück

Formulare · Form

Weiter →

Datenanzeige · Badge

Ü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