R

ReflexKR

Reflex Docs

The Component Workshop: Reflex's 100 Library Components

·

Course overview

Layout

·

Box

·

Flex

·

Stack

·

Grid

·

Container

·

Section

·

Center

·

Spacer

·

Separator

·

Card

·

Inset

·

Aspect Ratio

·

Fragment

Typography

·

Text

·

Heading

·

Link

·

Code

·

Markdown

·

Blockquote

·

Quote

·

Strong

·

Em

·

Kbd

Forms

·

Button

·

Input

·

Text Area

·

Checkbox

·

Switch

·

Radio Group

·

Select

·

Slider

·

Form

·

Upload

Data display

·

Badge

·

Avatar

·

Icon

·

Callout

·

List

·

Data List

·

Code Block

·

Progress

·

Spinner

·

Scroll Area

·

Moment

Disclosure

·

Tabs

·

Accordion

·

Segmented Control

Overlay

·

Tooltip

·

Popover

·

Hover Card

·

Dialog

·

Alert Dialog

·

Drawer

·

Dropdown Menu

·

Context Menu

·

Toast

Dynamic rendering

·

Cond

·

Match

·

Foreach

·

Auto Scroll

Media

·

Image

·

Audio

·

Video

Tables and data grids

·

Table

·

Data Table

·

Data Editor

Charts

·

Area Chart

·

Bar Chart

·

Line Chart

·

Composed Chart

·

Pie Chart

·

Radar Chart

·

Radial Bar Chart

·

Scatter Chart

·

Funnel Chart

·

Treemap

·

Sankey Chart

·

Error Bar

Chart parts

·

Axis

·

Cartesian Grid

·

Chart Tooltip

·

Legend

·

Label

·

Reference

·

Brush

Other chart libraries

·

Plotly

·

Pyplot

HTML elements

·

HTML

·

HTML Text

·

HTML Layout

·

HTML Forms

·

HTML Media

·

HTML Tables

·

SVG

Other tools

·

Theme

·

Skeleton

·

Clipboard

·

Html Embed

·

Script

·

Memo

The Component Workshop: Reflex's 100 Library Components
Overlay

Dialog

rx.dialog.rootrx.dialog.triggerrx.dialog.contentrx.dialog.titlerx.dialog.descriptionrx.dialog.close

Official docs

Workshop order

When someone clicks 'Edit profile' on a member card, open a window in the middle of the screen where they can change their name and email and save.

rx.dialog is a modal window that opens in the middle of the screen. While it is open, the page behind it dims and cannot be clicked, so the user can focus on the task inside. Use it for small jobs that should be finished in one go, such as editing details or adding a new item. It is built from six parts.

  • dialog.root: wraps all the parts of the dialog.
  • dialog.trigger: wraps the element that opens the dialog.
  • dialog.content: what goes inside the window.
  • dialog.title: the window's title. Screen readers announce it when the dialog opens.
  • dialog.description: the window's description, read out along with the title.
  • dialog.close: wraps an element that closes the window.
rx.dialog.root(
    rx.dialog.trigger(rx.button("Open Dialog")),
    rx.dialog.content(
        rx.dialog.title("Welcome to Reflex!"),
        rx.dialog.description(
            "This is a dialog component. You can render anything you want in here.",
        ),
        rx.dialog.close(
            rx.button("Close Dialog", size="3"),
        ),
    ),
)

Try the props

The window's size comes from size (padding) and max_width (maximum width) on dialog.content. Change a value, then open the window.

Try the props

title

description

size

max_width

rx.dialog.root(
    rx.dialog.trigger(rx.button("Open dialog")),
    rx.dialog.content(
        rx.dialog.title("Invite teammates"),
        rx.dialog.description("They will get an email with a link."),
        rx.flex(
            rx.dialog.close(rx.button("Close", variant="soft", color_scheme="gray")),
            justify="end",
            margin_top="16px",
        ),
        size="3",
        max_width="450px",
    ),
)
size changes the padding and the corner radius together. Set the width with style props such as max_width.
PartPropValuesUse
rootopenTrue / FalseDrive the open state from State (controlled); pair with on_open_change
rootdefault_openTrue / FalseOpen on first render (uncontrolled)
rooton_open_changeeventFires on open or close; passes the new open value (bool)
contentsize"1" ~ "4"Padding and corner size
contenton_open_auto_focus · on_close_auto_focuseventFocus moves into the window on open, back to the trigger on close
contenton_escape_key_downeventEsc is pressed
contenton_pointer_down_outside · on_interact_outsideeventA press or interaction outside the window

trigger, title, description and close only have the common events. Pressing Esc or clicking outside the window also closes it.

In practice: a window with inputs

A profile editor with inputs inside and Cancel and Save buttons side by side at the bottom. Both buttons are wrapped in dialog.close, so clicking either one closes the window. Actually saving the values is covered below in "Submitting a form" and in the order.

rx.dialog.root(
    rx.dialog.trigger(rx.button("Edit Profile", size="4")),
    rx.dialog.content(
        rx.dialog.title("Edit Profile"),
        rx.dialog.description(
            "Change your profile details and preferences.",
            size="2",
            margin_bottom="16px",
        ),
        rx.flex(
            rx.text("Name", as_="div", size="2", margin_bottom="4px", weight="bold"),
            rx.input(default_value="Freja Johnson", placeholder="Enter your name"),
            rx.text("Email", as_="div", size="2", margin_bottom="4px", weight="bold"),
            rx.input(default_value="freja@example.com", placeholder="Enter your email"),
            direction="column",
            spacing="3",
        ),
        rx.flex(
            rx.dialog.close(
                rx.button("Cancel", color_scheme="gray", variant="soft"),
            ),
            rx.dialog.close(
                rx.button("Save"),
            ),
            spacing="3",
            margin_top="16px",
            justify="end",
        ),
    ),
)

Wrap content such as a table in rx.inset(side="x") to run it out to the window's left and right edges.

rx.dialog.root(
    rx.dialog.trigger(rx.button("View users", size="4")),
    rx.dialog.content(
        rx.dialog.title("Users"),
        rx.dialog.description("The following users have access to this project."),
        rx.inset(
            rx.table.root(
                rx.table.header(
                    rx.table.row(
                        rx.table.column_header_cell("Full Name"),
                        rx.table.column_header_cell("Email"),
                        rx.table.column_header_cell("Group"),
                    ),
                ),
                rx.table.body(
                    rx.table.row(
                        rx.table.row_header_cell("Danilo Rosa"),
                        rx.table.cell("danilo@example.com"),
                        rx.table.cell("Developer"),
                    ),
                    rx.table.row(
                        rx.table.row_header_cell("Zahra Ambessa"),
                        rx.table.cell("zahra@example.com"),
                        rx.table.cell("Admin"),
                    ),
                ),
            ),
            side="x",
            margin_top="24px",
            margin_bottom="24px",
        ),
        rx.flex(
            rx.dialog.close(
                rx.button("Close", variant="soft", color_scheme="gray"),
            ),
            spacing="3",
            justify="end",
        ),
    ),
)

Reacting to open and close: on_open_change

The on_open_change of dialog.root passes True when it opens and False when it closes. Use it to count how often the window opens, or to clear the inputs when it closes.

Opened or closed: 0 times

Dialog open: no

class DialogEventState(rx.State):
    num_opens: int = 0
    opened: bool = False

    @rx.event
    def count_opens(self, value: bool):
        self.opened = value
        self.num_opens += 1


rx.flex(
    rx.text("Opened or closed: ", DialogEventState.num_opens, " times"),
    rx.text("Dialog open: ", rx.cond(DialogEventState.opened, "yes", "no")),
    rx.dialog.root(
        rx.dialog.trigger(rx.button("Open Dialog")),
        rx.dialog.content(
            rx.dialog.title("Welcome to Reflex!"),
            rx.dialog.description(
                "This is a dialog component. You can render anything you want in here.",
            ),
            rx.dialog.close(
                rx.button("Close Dialog", size="3"),
            ),
        ),
        on_open_change=DialogEventState.count_opens,
    ),
    direction="column",
    spacing="3",
    align="start",
)

Opening and closing from State

Bind open to a State value and code can open the window without any dialog.trigger. Below, two buttons each fill in a different value and open the window. With on_open_change wired up as well, State keeps up even when the window is closed with Esc or an outside click.

Opening a dialog from a dropdown menu item is covered on the Dropdown Menu page.

class DialogControlState(rx.State):
    open: bool = False
    seconds: int = 0

    @rx.event
    def set_open(self, value: bool):
        self.open = value

    @rx.event
    def start(self, seconds: int):
        self.seconds = seconds
        self.open = True

    @rx.event
    def confirm(self):
        self.open = False


rx.flex(
    rx.button("Break: 5 min", variant="soft", on_click=DialogControlState.start(5)),
    rx.button("Break: 15 min", variant="soft", on_click=DialogControlState.start(15)),
    rx.dialog.root(
        rx.dialog.content(
            rx.dialog.title("Take a break"),
            rx.dialog.description("Stand up and stretch for ", DialogControlState.seconds, " minutes."),
            rx.flex(rx.button("OK", on_click=DialogControlState.confirm), justify="end", margin_top="16px"),
            max_width="380px",
        ),
        open=DialogControlState.open,
        on_open_change=DialogControlState.set_open,
    ),
    spacing="3",
    wrap="wrap",
)

Submitting a form

Put an rx.form inside the window to collect all the inputs at once. Wrap the submit button in dialog.close and the window closes as the form is submitted. The handler receives a dict keyed by name and shows a notification (toast).

class DialogFormState(rx.State):
    current_user: dict[str, str] = {}

    @rx.event
    def add_user(self, form_data: dict):
        self.current_user = form_data
        # Here you would save the user to a database (see the Database course).
        return rx.toast.info(
            f"User {form_data.get('name', '')} has been added.", position="bottom-right"
        )


rx.dialog.root(
    rx.dialog.trigger(
        rx.button(rx.icon("plus", size=26), rx.text("Add User", size="4")),
    ),
    rx.dialog.content(
        rx.dialog.title("Add New User"),
        rx.dialog.description("Fill the form with the user's info"),
        rx.form(
            rx.flex(
                rx.input(placeholder="User Name", name="name"),
                rx.input(placeholder="user@reflex.dev", name="email"),
                rx.flex(
                    rx.dialog.close(
                        rx.button("Cancel", variant="soft", color_scheme="gray"),
                    ),
                    rx.dialog.close(
                        rx.button("Submit", type="submit"),
                    ),
                    spacing="3",
                    justify="end",
                ),
                direction="column",
                spacing="4",
            ),
            on_submit=DialogFormState.add_user,
            reset_on_submit=False,
        ),
        max_width="450px",
    ),
)

The official docs example saves to a database at the same point. Taken on its own, that part looks like this (it does not run on this page).

from sqlmodel import select


class User(rx.Model, table=True):
    name: str
    email: str


@rx.event
def add_user_to_db(self, form_data: dict):
    with rx.session() as session:
        if session.exec(select(User).where(User.email == form_data["email"])).first():
            return rx.window_alert("User with this email already exists")
        session.add(User(**form_data))
        session.commit()
    return rx.toast.info(f"User {form_data['name']} has been added.")

Filling the order

This time the window is controlled from State. The Save button is not wrapped in dialog.close; instead the submit handler saves the values and then closes the window with self.open = False. That way the window closes only after saving has finished. The inputs are pre-filled with the current values through default_value.

Freja Johnson

freja@example.com

class DialogProfileState(rx.State):
    open: bool = False
    name: str = "Freja Johnson"
    email: str = "freja@example.com"

    @rx.event
    def set_open(self, value: bool):
        self.open = value

    @rx.event
    def save(self, form_data: dict):
        self.name = form_data.get("name", "").strip() or self.name
        self.email = form_data.get("email", "").strip() or self.email
        self.open = False
        return rx.toast.success("Profile saved")


rx.card(
    rx.flex(
        rx.avatar(fallback="FJ", radius="full"),
        rx.box(
            rx.text(DialogProfileState.name, weight="bold"),
            rx.text(DialogProfileState.email, size="2", color_scheme="gray"),
        ),
        rx.spacer(),
        rx.dialog.root(
            rx.dialog.trigger(rx.button(rx.icon("pencil"), "Edit profile", variant="soft", size="2")),
            rx.dialog.content(
                rx.dialog.title("Edit profile"),
                rx.dialog.description("Change your name and email.", size="2", margin_bottom="12px"),
                rx.form(
                    rx.flex(
                        rx.input(name="name", default_value=DialogProfileState.name, placeholder="Name"),
                        rx.input(name="email", default_value=DialogProfileState.email, placeholder="Email", type="email"),
                        rx.flex(
                            rx.dialog.close(rx.button("Cancel", variant="soft", color_scheme="gray", type="button")),
                            rx.button("Save", type="submit"),
                            spacing="3",
                            justify="end",
                        ),
                        direction="column",
                        spacing="3",
                    ),
                    on_submit=DialogProfileState.save,
                ),
                max_width="420px",
            ),
            open=DialogProfileState.open,
            on_open_change=DialogProfileState.set_open,
        ),
        spacing="3",
        align="center",
    ),
    max_width="460px",
)

Workshop notes

  • root · trigger · content · title · description · close. Always include title (screen readers read it).

  • Size comes from content's size ("1"~"4") and max_width; use rx.inset for content that runs to the edges.

  • on_open_change passes the new open value (bool). Control it with open and you can open it without a trigger.

  • For forms, use rx.form + on_submit. To close on submit, wrap the button in close or set open to False in the handler.

  • For windows that must get an answer, such as confirm/cancel, use Alert Dialog.

← Previous

Overlay · Hover Card

Next →

Overlay · Alert Dialog

Get news about new lessons

We'll email you when this course gets new lessons or a follow-up course opens.

We collect: your email address · Purpose: news about new lessons or the launch of this course · Kept until: you ask us to remove it. We never use it for anything else or share it. Reply to any notice email and we'll delete it right away.

Questions & answers

Ask anything about this lesson. Questions from every language version of this page appear together.

Shown as plain text, without links or formatting.

R

ReflexKR

Learn Python and Reflex by doing. Every example was run and verified on Reflex 0.10.0.

ReflexKR is free, with no banner ads. If it helped you, you can support it with a coffee. ☕ Buy us a coffee

Affiliate disclosure
한국어English日本語简体中文EspañolDeutsch

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

Built with Reflex