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
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.
Drop files here or click to select
rx.upload(
rx.text("Drop files here or click to select"),
id="upload_basic",
border="2px dashed var(--gray-7)",
padding="2em",
)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
Drop files here or click to select
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")))| Prop | Werte | Standard | Verwendung |
|---|---|---|---|
id | String (fest) | "default" | Name dieser Dropzone. Die speziellen Vars · Events unten nutzen dieselbe id |
accept | {MIME: [Endungen...]} | alle Dateien | Erlaubte Dateitypen |
multiple | True / False | True | Mehrfachauswahl |
max_files | Ganzzahl | unbegrenzt | Höchstzahl an Dateien |
max_size, min_size | Bytes | unbegrenzt | Maximal- · Mindestgröße einer Datei |
disabled | True / False | False | Dropzone abschalten |
no_click, no_drag, no_keyboard | True / False | False | Klicken / Hineinziehen / Öffnen per Tastatur verhindern |
drag_active_style | Style-dict | keiner | Stil, während eine Datei hineingezogen wird (0.10) |
on_drop | Event | merkt sich die Auswahl | Läuft sofort, wenn Dateien abgelegt oder gewählt werden |
on_drop_rejected | Event | Fehler-Toast | Wenn Dateien die Bedingungen nicht erfüllen |
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.
Drag and drop files here or click to select files
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"),
)Ü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.
Drop up to 5 files (2 MB each)
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"),
),
)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./_upload für alle öffentlich. Speichere am besten mit zufälligem Präfix, damit sich gleiche Namen nicht überschreiben.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.
Images or PDF only, up to 3 files
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")),
)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.
Click to upload
or drag and drop
SVG, PNG, JPG or GIF (MAX. 5MB)
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"),
)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.
Drag and drop files here or click to select files
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%",
)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")))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.
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)
Drop or click to attach
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.
Über neue Lektionen informiert werden
Wir schicken dir eine E-Mail, wenn dieser Kurs neue Lektionen bekommt oder ein Folgekurs erscheint.
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© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Fragen & Antworten
Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.