コンポーネント工房: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
工房の注文書
返金申請の画面に、レシート写真の添付欄を作ってください。画像は3枚まで、ドラッグ&ドロップですぐ添付され、名前とサイズが表示されるようにしてください。
rx.uploadは、ファイルをドラッグ&ドロップするかクリックして選ぶ領域(ドロップゾーン)です。選んだファイルはまずブラウザに留まり、イベントハンドラーにrx.upload_files()を渡したときに初めてサーバーへ送られます。サーバーのハンドラーはファイルのリストを受け取って、読んだり保存したりします。
この講座の例は共用サーバーで動いているので、ファイルは保存せず、名前とサイズだけを表示します。保存するコードは別途コードとしてだけ示します。
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",
)ドロップゾーンの動作を決めるプロパティです。ファイルをいくつか選んでから、multipleをオフにしたりno_clickをオンにしたりしてみてください。選んだファイル名は下にバッジで表示されます。
プロパティを変えてみる
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")))| プロパティ | 値 | 既定値 | 用途 |
|---|---|---|---|
id | 文字列(固定) | "default" | このドロップゾーンの名前。下の特殊な Var・イベントが同じ id を使う |
accept | {MIME: [拡張子...]} | すべてのファイル | 受け付けるファイルの種類 |
multiple | True / False | True | 複数選択 |
max_files | 整数 | 制限なし | 最大ファイル数 |
max_size, min_size | バイト | 制限なし | ファイル1つの最大・最小サイズ |
disabled | True / False | False | ドロップゾーンを無効にする |
no_click, no_drag, no_keyboard | True / False | False | クリック / ドラッグ&ドロップ / キーボードで開くのを止める |
drag_active_style | スタイルの dict | なし | ファイルをドラッグして入ってきたときのスタイル(0.10) |
on_drop | イベント | 選んだファイルを保持 | ファイルをドロップするか選んだらすぐ実行 |
on_drop_rejected | イベント | エラーのトースト | 条件に合わないファイルが入ってきたとき |
ファイルを選ぶと、ブラウザ側のリストに入ります。特殊な Var のrx.selected_files(id)でその名前のリストを画面に表示し、特殊なイベントのrx.clear_selected_files(id)で空にします。まだサーバーには何も送られていません。
idはドロップゾーンと特殊な Var・イベントをつなぐ鍵なので、すべて同じ値でなければなりません。文字列かモジュールの定数で書き、State 変数は使えません。ドロップゾーン内の「Select File」ボタンは見た目だけです。ドロップゾーンの中をどこでもクリックすれば、ファイル選択ウィンドウが開くからです。
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"),
)ハンドラーにrx.upload_files(upload_id=id)を渡すと、選んだファイルがサーバーへ送られます。ハンドラーはfiles: list[rx.UploadFile]をひとつ受け取る普通のasyncイベントです。ファイルごとにfile.name(元の名前)、file.size(バイト)、await file.read()(中身)が使えます。
アップロードのハンドラーには@rx.event(background=True)を付けず、async with selfも使いません。それらは下のチャンクアップロードでだけ使います。また、アップロードボタンはドロップゾーンの外に置きます。中に置くと、押すたびにファイルウィンドウが開きつつアップロードも始まってしまいます。
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"),
),
)実際のアプリでは、rx.get_upload_dir()が返すフォルダー(Path、既定は./uploaded_files、環境変数REFLEX_UPLOADED_FILES_DIRで変更可)に保存し、画面ではrx.get_upload_url(ファイル名)でアドレスを作ってrx.imageなどに渡します。下は公式ドキュメントの基本の例で、このページでは実行しません。
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():サーバー(バックエンド)で保存する場所。イベントハンドラーで使います。rx.get_upload_url(name):画面(フロントエンド)で使うアドレス。Python の文字列ではなくフロントエンドの Var なので、コンポーネントのコードでだけ使います。アドレスは/_upload/ファイル名の形ですが、直接書かずにこの関数を通します。/_uploadで誰にでも公開されます。同じ名前がぶつからないよう、ランダムな接頭辞を付けて保存するのがよいでしょう。on_dropにハンドラーをつなぐと、ボタンなしで選んだ瞬間にアップロードされます。acceptでファイルの種類を、max_filesとmax_sizeで個数とサイズを絞り込みます。条件に合わないファイルはon_drop_rejectedに届き、このイベントを渡さないと 0.10 はエラーのトーストを表示します。下に、画像でも PDF でもないファイルや 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は既定の枠線と余白があるドロップゾーンです。見た目を一から自分で作りたいならrx.upload.rootを使います。プロパティとイベントは同じです。drag_active_styleで、ファイルをドラッグして入ってきている間の背景を変えられます。
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(とrx.upload_files_chunk)はon_upload_progressを受け取ります。アップロード中は{"loaded": 送ったバイト数, "total": 全体のバイト数, "progress": 0~1}の dict がこのハンドラーに届きます。rx.cancel_upload(id)はアップロード中の処理を止めます。このページは 2MB 以下しか受け付けないので、進捗はすぐ 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()はファイル全体をメモリに載せます。大きなファイルはrx.upload_files_chunk(upload_id=...)で少しずつ受け取り、そのままディスクに書き込みます。チャンクのハンドラーは@rx.event(background=True)でなければならず、chunk_iter: rx.UploadChunkIteratorを受け取って最後まで読む必要があります(途中で戻るとアップロードが失敗します)。チャンクごとにchunk.filename、chunk.offset、chunk.content_type、chunk.dataがあります。ディスクに書き込むコードなので、ここでは実行しません。
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")))フォームのon_submitにはファイルは入りません。フォームの中にドロップゾーンがあるなら、ファイルはtype="button"のボタンやon_dropでrx.upload_files()を使って別に送り、残りの欄だけをon_submitで受け取ります。
画像の種類と個数・サイズはドロップゾーンが先に絞り込み、on_dropでドロップした瞬間にサーバーが名前とサイズを受け取ります。リストは最新の3件だけを残します。保存はしません。
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",
)工房ノート
rx.upload(id="固定の id") でドロップゾーン。選んだファイルは rx.selected_files(id)、空にするのは rx.clear_selected_files(id)。
送信はハンドラー(rx.upload_files(upload_id=id))。ハンドラーは async で files: list[rx.UploadFile]。
on_drop なら選んだ瞬間にアップロード。accept · max_files · max_size で絞り込み、はじかれたものは on_drop_rejected。
保存は rx.get_upload_dir()、表示は rx.get_upload_url()。アップロードフォルダーは公開される。
アップロードボタンはドロップゾーンの外に。大きなファイルは rx.upload_files_chunk + background ハンドラー。
新しい授業・講座のお知らせを受け取る
この講座に授業が追加されたり、続きの講座が公開されたりしたらメールでお知らせします。
R
ReflexKR
触って学ぶPythonとReflex。すべての例はReflex 0.10.0で実際に動かして確認しています。
ReflexKRはバナー広告なし・無料で運営しています。役に立ったら、コーヒー一杯で応援してください。 ☕ コーヒーで応援する
アフィリエイトと支援について© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
質問と回答
この回について気になることを書き込んでください。すべての言語のページの質問がここに集まります。