R

ReflexKR

Reflex公式ドキュメント

コンポーネント工房: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

コンポーネント工房:Reflexライブラリ100
フォーム

Form

rx.formrx.form.rootrx.form.fieldrx.form.labelrx.form.controlrx.form.messagerx.form.submit

公式ドキュメント

工房の注文書

カフェの注文フォームを作ってください。名前、カップのサイズ、受け取り方法、リクエスト、ギフト包装をまとめて受け取って受け付けるようにしてください。

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つの場合を比べてみてください。

プロパティを変えてみる

Submitted: -

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,
)
no_validate=True ならブラウザのチェックなしで送信されます。その場合はサーバー(ハンドラー)側で自分でチェックしなければなりません。
プロパティ値既定値用途
on_submitイベント送信したとき。フォームデータの dict(または TypedDict)を渡す
reset_on_submitTrue / FalseFalse送信後に欄を空にする
no_validateTrue / FalseFalseブラウザのチェックを切る
as_childTrue / FalseFalseフォームの代わりに子要素としてレンダリング
name, id文字列なしフォームの名前。idがあれば、別の場所の入力がform="id"で加われる
action, method, enc_type, targetHTML フォームの属性。Reflex は送信を横取りするので普通は使わない
handle_submit_unique_name文字列自動このフォームの送信関数の名前を自分で決めるとき
on_clear_server_errorsイベント低水準フォームでサーバーエラーの表示が消えるとき

name と id

フォームデータは2か所から集まります。ひとつはnameで、ブラウザが送信する値です。チェックボックス・スイッチ・ラジオのように「オンのときだけ値がある」入力は、オンでなければキーごと入りません。そのためform_data.get(...)で読みます。

もうひとつはidです。0.10 ではidがある入力もそのidをキーとして入れ、オンでなくても値が入ります。チェックボックスはTrue/Falseになります。標準のinput・select・textareaは自動でこのように集められ、自作の入力コンポーネントはクラスに_is_form_control = Trueを置く必要があります。ただし公式ドキュメントは、このid方式が 0.11 でなくなると予告しています。今後はnameだけを使うほうが安全です。

Results: {}

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

TypedDict でフォームデータをチェックする

ハンドラーの引数を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 off

動的なフォーム

rx.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())),
)

低水準フォーム:field · label · control · message · submit

公式ドキュメントは低水準フォームを実験段階としており、本番のサービスには上の高水準フォームを勧めています。低水準フォームの利点は、チェックのメッセージを入力の横に自動でつないでくれることです。構造は次のとおりです。

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)。

← 前へ

フォーム · Slider

次へ →

フォーム · Upload

新しい授業・講座のお知らせを受け取る

この講座に授業が追加されたり、続きの講座が公開されたりしたらメールでお知らせします。

収集項目:メールアドレス・目的:この講座の新しい授業・公開のお知らせ・保有期間:解除のご依頼まで。他の目的には使わず、第三者にも提供しません。お知らせメールに返信いただければすぐに削除します。

質問と回答

この回について気になることを書き込んでください。すべての言語のページの質問がここに集まります。

リンクや書式なしで、そのままの文字で表示されます。

R

ReflexKR

触って学ぶPythonとReflex。すべての例はReflex 0.10.0で実際に動かして確認しています。

ReflexKRはバナー広告なし・無料で運営しています。役に立ったら、コーヒー一杯で応援してください。 ☕ コーヒーで応援する

アフィリエイトと支援について
한국어English日本語简体中文EspañolDeutsch

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

Built with Reflex