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
rx.dialog.rootrx.dialog.triggerrx.dialog.contentrx.dialog.titlerx.dialog.descriptionrx.dialog.closeOfficial 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"),
),
),
)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",
),
)| Part | Prop | Values | Use |
|---|---|---|---|
root | open | True / False | Drive the open state from State (controlled); pair with on_open_change |
root | default_open | True / False | Open on first render (uncontrolled) |
root | on_open_change | event | Fires on open or close; passes the new open value (bool) |
content | size | "1" ~ "4" | Padding and corner size |
content | on_open_auto_focus · on_close_auto_focus | event | Focus moves into the window on open, back to the trigger on close |
content | on_escape_key_down | event | Esc is pressed |
content | on_pointer_down_outside · on_interact_outside | event | A 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.
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",
),
),
)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",
)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",
)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.")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.
Get news about new lessons
We'll email you when this course gets new lessons or a follow-up course opens.
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© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Questions & answers
Ask anything about this lesson. Questions from every language version of this page appear together.