コンポーネント工房: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
工房の注文書
カフェの注文フォームを作ってください。名前、カップのサイズ、受け取り方法、リクエスト、ギフト包装をまとめて受け取って受け付けるようにしてください。
rx.formは複数の入力をまとめて一度に送信する枠です。中にrx.input、rx.checkbox、rx.switch、rx.slider、rx.text_area、rx.radio_group、rx.selectなどの入力を入れ、それぞれにnameを付けます。送信するとon_submitハンドラーが{name: 値}の dict をひとつ受け取ります。
送信はtype="submit"のボタンを押すか、入力欄で Enter を押したときに起こります。送信ボタン(またはtype="submit"の入力)は必ず必要です。reset_on_submit=Trueなら送信後に欄が空になります。
Results: {}
class FormBasicState(rx.State):
form_data: dict = {}
@rx.event
def handle_submit(self, form_data: dict):
self.form_data = form_data
rx.vstack(
rx.form(
rx.vstack(
rx.input(placeholder="First Name", name="first_name"),
rx.input(placeholder="Last Name", name="last_name"),
rx.hstack(
rx.checkbox("Checked", name="check"),
rx.text(rx.flex(rx.switch(name="switch"), "Switched", spacing="2"), as_="label", size="2"),
spacing="4",
),
rx.button("Submit", type="submit"),
),
on_submit=FormBasicState.handle_submit,
reset_on_submit=True,
),
rx.divider(),
rx.text("Results: ", rx.code(FormBasicState.form_data.to_string())),
)オフのチェックボックスとスイッチは結果に含まれません。オンにすると"on"として入ります。理由は下の「name と id」で説明します。
フォームを送信する前に、ブラウザがrequired、type="email"、min・maxなどの条件を先にチェックします。no_validateをオンにするとこのチェックを飛ばして、そのままハンドラーに送ります。空欄やabc、500を入れて、2つの場合を比べてみてください。
プロパティを変えてみる
required
no_validate
rx.form(
rx.vstack(
rx.input(
name="email",
type="email",
placeholder="Email",
required=True,
),
rx.input(name="age", type="number", min=1, max=120, placeholder="Age (1-120)"),
rx.button("Submit", type="submit"),
rx.text("Submitted: ", rx.code(FormLabState.result), size="2"),
),
on_submit=FormLabState.handle_submit,
no_validate=False,
)| プロパティ | 値 | 既定値 | 用途 |
|---|---|---|---|
on_submit | イベント | 送信したとき。フォームデータの dict(または TypedDict)を渡す | |
reset_on_submit | True / False | False | 送信後に欄を空にする |
no_validate | True / False | False | ブラウザのチェックを切る |
as_child | True / False | False | フォームの代わりに子要素としてレンダリング |
name, id | 文字列 | なし | フォームの名前。idがあれば、別の場所の入力がform="id"で加われる |
action, method, enc_type, target | HTML フォームの属性。Reflex は送信を横取りするので普通は使わない | ||
handle_submit_unique_name | 文字列 | 自動 | このフォームの送信関数の名前を自分で決めるとき |
on_clear_server_errors | イベント | 低水準フォームでサーバーエラーの表示が消えるとき |
フォームデータは2か所から集まります。ひとつはnameで、ブラウザが送信する値です。チェックボックス・スイッチ・ラジオのように「オンのときだけ値がある」入力は、オンでなければキーごと入りません。そのためform_data.get(...)で読みます。
もうひとつはidです。0.10 ではidがある入力もそのidをキーとして入れ、オンでなくても値が入ります。チェックボックスはTrue/Falseになります。標準のinput・select・textareaは自動でこのように集められ、自作の入力コンポーネントはクラスに_is_form_control = Trueを置く必要があります。ただし公式ドキュメントは、このid方式が 0.11 でなくなると予告しています。今後はnameだけを使うほうが安全です。
class FormNameIdState(rx.State):
form_data: dict = {}
@rx.event
def handle_submit(self, form_data: dict):
self.form_data = form_data
rx.form(
rx.vstack(
rx.checkbox("By name", name="by_name"),
rx.checkbox("By id", id="form_by_id_box"),
rx.button("Submit", type="submit"),
rx.text("Results: ", rx.code(FormNameIdState.form_data.to_string())),
),
on_submit=FormNameIdState.handle_submit,
)チェックボックスのグループのように複数の入力が同じnameを使うと、dict には最後の値だけが残ります。公式ドキュメント(最新版)はハンドラーの引数をrx.form.FormDataと書き、getlistですべて受け取る方法を示しています。しかしインストールされている 0.10 にはrx.form.FormDataがないので、下のコードは参考として載せておくだけにします。
class ToppingsState(rx.State):
toppings: list[str] = []
@rx.event
def handle_submit(self, form_data: rx.form.FormData[str, str]):
self.toppings = form_data.getlist("topping")
def toppings_form():
return rx.form(
rx.el.input(type="checkbox", name="topping", value="cheese"),
rx.el.input(type="checkbox", name="topping", value="olives"),
rx.button("Submit", type="submit"),
on_submit=ToppingsState.handle_submit,
)0.10 では入力ごとに別の名前を付け(topping_cheese …)、ハンドラーで接頭辞を見て選び集めれば済みます。
FORM_TOPPINGS = ["cheese", "olives", "mushrooms"]
class FormToppingState(rx.State):
toppings: list[str] = []
@rx.event
def handle_submit(self, form_data: dict):
self.toppings = [k.removeprefix("topping_") for k in form_data if k.startswith("topping_")]
rx.form(
rx.vstack(
rx.hstack(*[rx.checkbox(t, name="topping_" + t) for t in FORM_TOPPINGS], spacing="4"),
rx.button("Submit", type="submit"),
rx.hstack(rx.foreach(FormToppingState.toppings, lambda t: rx.badge(t))),
),
on_submit=FormToppingState.handle_submit,
)ハンドラーの引数をdictの代わりにTypedDictで書くと、エディターがキーを補完し、Reflex がフォームを作るときにチェックします。TypedDict の必須キーごとに、同じname(またはid)の入力がフォームになければならず、ひとつでも欠けるとアプリが起動する前にEventHandlerValueErrorが出ます。あってもなくてもよいキーはNotRequiredで示します。
from typing import TypedDict
from typing_extensions import NotRequired
class FormContactData(TypedDict):
first_name: str
last_name: str
email: str
message: NotRequired[str]
class FormTypedState(rx.State):
greeting: str = ""
@rx.event
def handle_submit(self, form_data: FormContactData):
self.greeting = f"Hi {form_data['first_name']} {form_data['last_name']} <{form_data['email']}>"
rx.vstack(
rx.form(
rx.vstack(
rx.input(placeholder="First Name", name="first_name"),
rx.input(placeholder="Last Name", name="last_name"),
rx.input(placeholder="Email", name="email", type="email"),
rx.text_area(placeholder="Message", name="message"),
rx.button("Submit", type="submit"),
),
on_submit=FormTypedState.handle_submit,
reset_on_submit=True,
),
rx.text(FormTypedState.greeting),
)下のようにemailの入力を入れ忘れると、エラーメッセージに期待したキー・足りないキー・見つかったキーがすべて表示されます。チェックはnameが固定の文字列のときだけ行われ、rx.foreachで作った動的な名前や、フォームにidがあるときは飛ばされます。実行時のform_dataは常に普通の dict です。
class SignupForm(TypedDict):
username: str
email: str
class SignupState(rx.State):
@rx.event
def handle_submit(self, form_data: SignupForm): ...
# EventHandlerValueError: the form has no control named "email".
rx.form(
rx.input(name="username"),
rx.button("Submit", type="submit"),
on_submit=SignupState.handle_submit,
)公式ドキュメント(最新版)は、TypedDict のlist[str]フィールドに同じ名前の値をすべて集め、boolフィールドはオフのチェックボックスをFalseで埋めると説明しています。この補完は 0.10 にはありません。0.10 の TypedDict は上の「作るときのチェック」だけを行い、オフのチェックボックスは相変わらずキーが入らないので、NotRequiredと.get()で扱います。
class PreferencesForm(TypedDict):
toppings: list[str] # every checked "toppings" checkbox
subscribe: bool # False when the checkbox is unchecked
notify: bool | None # None when the switch is offrx.foreachで State のリストを回して入力を作ると、ユーザーが欄を増やせるフォームになります。追加した欄もすべて送信データに入ります。ここでは欄は8個までしか増やせないようにしてあります。フォームを2つ並べ、下のフォームは欄の追加だけを担当します。
Results: {}
class FormDynamicState(rx.State):
form_data: dict = {}
form_fields: list[str] = ["first_name", "last_name", "email"]
@rx.var
def form_field_placeholders(self) -> list[str]:
return [" ".join(w.capitalize() for w in field.split("_")) for field in self.form_fields]
@rx.event
def add_form_field(self, form_data: dict):
new_field = form_data.get("new_field")
if not new_field or len(self.form_fields) >= 8:
return
field_name = new_field.strip().lower().replace(" ", "_")
if field_name and field_name not in self.form_fields:
self.form_fields.append(field_name)
@rx.event
def handle_submit(self, form_data: dict):
self.form_data = form_data
rx.vstack(
rx.form(
rx.vstack(
rx.foreach(
FormDynamicState.form_fields,
lambda field, idx: rx.input(
placeholder=FormDynamicState.form_field_placeholders[idx],
name=field,
),
),
rx.button("Submit", type="submit"),
),
on_submit=FormDynamicState.handle_submit,
reset_on_submit=True,
),
rx.form(
rx.hstack(
rx.input(placeholder="New Field", name="new_field"),
rx.button("+", type="submit", variant="soft"),
),
on_submit=FormDynamicState.add_form_field,
reset_on_submit=True,
),
rx.divider(),
rx.text("Results: ", rx.code(FormDynamicState.form_data.to_string())),
)公式ドキュメントは低水準フォームを実験段階としており、本番のサービスには上の高水準フォームを勧めています。低水準フォームの利点は、チェックのメッセージを入力の横に自動でつないでくれることです。構造は次のとおりです。
form.root(
form.field(
form.label(...),
form.control(...),
form.message(...),
),
form.submit(...),
)rx.form.root:フォーム全体。on_submit、reset_on_submitは高水準フォームと同じです。rx.form.field:入力ひとつと、そのラベル・メッセージのまとまり。nameが内側の入力の名前になり、server_invalidでサーバー側チェックの失敗を示します。rx.form.label:ラベル。rx.form.control:入力の場所。別のコンポーネントを使うならas_child=Trueで包みます。0.10 では control の中にはrx.inputしか入りません(チェックボックス・select などは field の外か root の直下に置きます)。rx.form.message:チェックのメッセージ。matchでどの失敗のときに表示するかを決めます("valueMissing"、"typeMismatch"、"patternMismatch"、"tooShort"、"rangeOverflow" …)。force_matchはサーバー側チェック用です。rx.form.submit:送信ボタン。別のボタンを使うならas_child=True。type="email"とrequiredをブラウザがチェックし、該当するmatchのメッセージが表示されます。チェックに失敗すると、送信ボタンを押しても送信されません。0.10 のrx.form.messageは文字色が白に固定されていて明るい画面では見えないので、例ではcolorを別に指定しています。
Submitted:
class FormLowState(rx.State):
email: str = ""
@rx.event
def handle_submit(self, form_data: dict):
self.email = form_data.get("email", "")
rx.vstack(
rx.form.root(
rx.form.field(
rx.flex(
rx.form.label("Email"),
rx.form.control(
rx.input(placeholder="Email Address", type="email", required=True),
as_child=True,
),
rx.form.message("Please enter your email", match="valueMissing", color=rx.color("red", 11)),
rx.form.message("Please enter a valid email", match="typeMismatch", color=rx.color("red", 11)),
rx.form.submit(rx.button("Submit"), as_child=True),
direction="column",
spacing="2",
align="stretch",
),
name="email",
),
on_submit=FormLowState.handle_submit,
reset_on_submit=True,
width="280px",
),
rx.text("Submitted: ", rx.code(FormLowState.email), size="2"),
)サーバー側のチェックは State の計算変数で行います。「不正」を表す bool の計算変数を、field のserver_invalidと message のforce_matchの両方につなぎます。force_matchはmatchがあるときだけ使われます(0.10 はmatchがないとforce_matchを外して渡し、matchのないメッセージは常に表示されます)。そこで、テキスト入力では自然には起こらないmatch="badInput"を指定し、表示するかどうかはforce_matchに決めさせます。下でadminと入力してみてください。メッセージが出て、ボタンが押せなくなります。
Signed up:
class FormServerCheckState(rx.State):
username: str = ""
submitted: str = ""
@rx.event
def set_username(self, value: str):
self.username = value
@rx.var
def username_taken(self) -> bool:
return self.username.strip().lower() in ["admin", "root", "reflex"]
@rx.event
def handle_submit(self, form_data: dict):
if not self.username_taken:
self.submitted = form_data.get("username", "")
self.username = ""
rx.vstack(
rx.form.root(
rx.form.field(
rx.flex(
rx.form.label("Username (try 'admin')"),
rx.form.control(
rx.input(
placeholder="Username",
value=FormServerCheckState.username,
on_change=FormServerCheckState.set_username,
required=True,
),
as_child=True,
),
rx.form.message(
"This name is taken",
match="badInput",
force_match=FormServerCheckState.username_taken,
color=rx.color("red", 11),
),
rx.form.submit(rx.button("Sign up", disabled=FormServerCheckState.username_taken), as_child=True),
direction="column",
spacing="2",
align="stretch",
),
name="username",
server_invalid=FormServerCheckState.username_taken,
),
on_submit=FormServerCheckState.handle_submit,
width="280px",
),
rx.text("Signed up: ", rx.code(FormServerCheckState.submitted), size="2"),
)フォームのon_submitにはアップロードしたファイルは入りません。フォームの中に Upload があるなら、ファイルはtype="button"の別のボタンやon_dropでrx.upload_files()を使って送り、残りの欄だけをon_submitで受け取ります。詳しくは Upload のページで扱います。
5種類の入力にそれぞれnameを付け、ひとつのハンドラーで受け取ります。select はrequiredなので選ぶまで送信されず、オフのチェックボックスはキーが入らないので.get("gift")で読んで"yes"/"no"に変換します。
class FormOrderState(rx.State):
last_order: dict = {}
@rx.event
def place_order(self, form_data: dict):
self.last_order = {
"name": form_data.get("name", ""),
"size": form_data.get("size", ""),
"pickup": form_data.get("pickup", ""),
"gift": "yes" if form_data.get("gift") else "no",
"note": form_data.get("note", "")[:100],
}
rx.card(
rx.form(
rx.vstack(
rx.input(name="name", placeholder="Your name", required=True),
rx.select(["Small", "Medium", "Large"], name="size", placeholder="Cup size", required=True),
rx.radio_group(["Store", "Delivery"], name="pickup", default_value="Store"),
rx.text_area(name="note", placeholder="Note (optional)", max_length=100, rows="2"),
rx.checkbox("Gift wrap", name="gift"),
rx.button("Place order", type="submit"),
rx.cond(
FormOrderState.last_order,
rx.code_block(FormOrderState.last_order.to_string(), language="json", width="100%"),
),
align="stretch",
),
on_submit=FormOrderState.place_order,
reset_on_submit=True,
),
width="100%",
max_width="360px",
)工房ノート
rx.form(..., on_submit=ハンドラー) + 中の入力ごとに name。ハンドラーは {name: 値} の dict を受け取る。
type="submit" のボタンが必ず必要。reset_on_submit=True なら送信後に空にする。
オフのチェックボックス・スイッチ・ラジオはキーが入らない。form_data.get() で読む。id 方式は 0.11 でなくなる。
ハンドラーの引数を TypedDict で書くと、フォームを作るときに足りない欄を見つけてくれる。
チェックのメッセージを入力の横につなぐなら、低水準の field · control · message(match、force_match + server_invalid)。
新しい授業・講座のお知らせを受け取る
この講座に授業が追加されたり、続きの講座が公開されたりしたらメールでお知らせします。
R
ReflexKR
触って学ぶPythonとReflex。すべての例はReflex 0.10.0で実際に動かして確認しています。
ReflexKRはバナー広告なし・無料で運営しています。役に立ったら、コーヒー一杯で応援してください。 ☕ コーヒーで応援する
アフィリエイトと支援について© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
質問と回答
この回について気になることを書き込んでください。すべての言語のページの質問がここに集まります。