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
Workshop order
When someone clicks the 'Comment' button under a post, open a small panel right there so they can write a short comment without leaving the page.
rx.popover is a small panel that pops up next to a button when you click it. Unlike a tooltip, it opens on click, and it can hold things to interact with, such as inputs and buttons. It is built from four parts.
popover.root: wraps all the parts of the popover.popover.trigger: wraps the button that opens and closes the popover.popover.content: the content that pops up when it is open.popover.close: wraps a button that closes the popover.rx.popover.root(
rx.popover.trigger(
rx.button("Popover"),
),
rx.popover.content(
rx.flex(
rx.text("Simple Example"),
rx.popover.close(
rx.button("Close"),
),
direction="column",
spacing="3",
),
),
)Placement and size go on popover.content; whether it is modal goes on popover.root. Change a value, then click Open to see it.
Try the props
text
size
side
align
modal
rx.popover.root(
rx.popover.trigger(rx.button("Open")),
rx.popover.content(
rx.flex(
rx.text("Simple Example"),
rx.popover.close(rx.button("Close", size="1")),
direction="column",
spacing="3",
),
size="2",
side="bottom",
align="center",
),
modal=False,
)| 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 | modal | True / False | Block interaction outside while open and hide the outside from screen readers |
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 | side · align | position name | Side it opens on and where it lines up (default "bottom", "center") |
content | side_offset · align_offset | number (px) | Gap from the button; extra shift from the aligned position |
content | avoid_collisions · collision_padding | bool · number | Move away from the screen edge, and the margin for that |
content | sticky · hide_when_detached | "partial" "always" · bool | How closely it sticks while scrolling; hide when the button is covered |
content | on_open_auto_focus · on_close_auto_focus | event | Focus moves inside on open, back to the button on close |
content | on_escape_key_down · on_pointer_down_outside | event | Esc key; a press outside |
content | on_focus_outside · on_interact_outside | event | Focus or interaction moves outside |
trigger and close have no props of their own; they only take the common events (on_click and so on).
A popover can hold anything. Here is a comment panel with an input, a checkbox and a send button next to an avatar. Wrapping the send button in popover.close closes the panel the moment it is clicked. Set the width directly on content, as in style={"width": 360}.
rx.popover.root(
rx.popover.trigger(
rx.button("Comment", variant="soft"),
),
rx.popover.content(
rx.flex(
rx.avatar("2", fallback="RX", radius="full"),
rx.box(
rx.text_area(placeholder="Write a comment…", style={"height": 80}),
rx.flex(
rx.checkbox("Send to group"),
rx.popover.close(rx.button("Comment", size="1")),
spacing="3",
margin_top="12px",
justify="between",
),
flex_grow="1",
),
spacing="3",
),
style={"width": 360},
),
)Put rx.inset(side="top") as the first child of content to add a header image that runs all the way to the edges, with no top padding.
rx.popover.root(
rx.popover.trigger(
rx.button("Feedback", variant="classic"),
),
rx.popover.content(
rx.inset(
side="top",
background="url('https://picsum.photos/id/29/720/200') center/cover",
height="100px",
),
rx.box(
rx.text_area(placeholder="Write a comment…", style={"height": 80}),
rx.flex(
rx.checkbox("Send to group"),
rx.popover.close(rx.button("Comment", size="1")),
spacing="3",
margin_top="12px",
justify="between",
),
padding_top="12px",
),
style={"width": 360},
),
)To use a State value as the button label, wrap it once in rx.text, as in rx.button(rx.text(State.value)). The official docs note that passing it directly, as in rx.button(State.value), does not work as expected. Pick a language below and the button label changes.
class PopoverLanguageState(rx.State):
language: str = "EN"
@rx.event
def set_language(self, value: str):
self.language = value
rx.popover.root(
rx.popover.trigger(
rx.button(rx.text(PopoverLanguageState.language), variant="outline"),
),
rx.popover.content(
rx.flex(
rx.popover.close(rx.button("EN", size="1", variant="soft", on_click=PopoverLanguageState.set_language("EN"))),
rx.popover.close(rx.button("KO", size="1", variant="soft", on_click=PopoverLanguageState.set_language("KO"))),
rx.popover.close(rx.button("JA", size="1", variant="soft", on_click=PopoverLanguageState.set_language("JA"))),
spacing="2",
),
),
)The on_open_change of popover.root passes True to the handler when it opens and False when it closes.
Opened or closed: 0 times
Popover open: no
class PopoverEventState(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: ", PopoverEventState.num_opens, " times"),
rx.text("Popover open: ", rx.cond(PopoverEventState.opened, "yes", "no")),
rx.popover.root(
rx.popover.trigger(
rx.button("Popover"),
),
rx.popover.content(
rx.flex(
rx.text("Simple Example"),
rx.popover.close(
rx.button("Close"),
),
direction="column",
spacing="3",
),
),
on_open_change=PopoverEventState.count_opens,
),
direction="column",
spacing="3",
align="start",
)Wire up open and on_open_change together and the pencil button and outside clicks work as usual, while your code can also close the panel. Below, change the name and click Rename: the handler saves the value and closes the panel with self.open = False. Each time it opens, the input is refilled with the current name.
class PopoverControlState(rx.State):
open: bool = False
name: str = "Untitled"
draft: str = ""
@rx.event
def set_open(self, value: bool):
self.open = value
if value:
self.draft = self.name
@rx.event
def set_draft(self, value: str):
self.draft = value
@rx.event
def rename(self):
self.name = self.draft.strip() or "Untitled"
self.open = False
rx.flex(
rx.heading(PopoverControlState.name, size="4"),
rx.popover.root(
rx.popover.trigger(rx.icon_button(rx.icon("pencil"), variant="soft", size="1", aria_label="Rename")),
rx.popover.content(
rx.flex(
rx.input(value=PopoverControlState.draft, on_change=PopoverControlState.set_draft),
rx.button("Rename", size="1", on_click=PopoverControlState.rename),
spacing="2",
),
),
open=PopoverControlState.open,
on_open_change=PopoverControlState.set_open,
),
align="center",
spacing="3",
)A small comment panel that opens from the 'Comment' button under a post. Set side="top" so it opens upward and align="start" so it lines up with the left edge of the button.
Shipping is now free over $30.
rx.card(
rx.text("Shipping is now free over $30.", size="2"),
rx.popover.root(
rx.popover.trigger(rx.button(rx.icon("message-circle"), "Comment", variant="ghost", size="1", margin_top="8px")),
rx.popover.content(
rx.flex(
rx.text_area(placeholder="Write a comment…", rows="3"),
rx.flex(
rx.popover.close(rx.button("Cancel", size="1", variant="soft", color_scheme="gray")),
rx.popover.close(rx.button("Post", size="1")),
spacing="2",
justify="end",
),
direction="column",
spacing="2",
),
side="top",
align="start",
width="300px",
),
),
max_width="360px",
)Workshop notes
Four parts: root · trigger · content · close. Clicking trigger opens it; clicking close or anywhere outside closes it.
Placement and size go on content (side, align, side_offset, size); modal goes on root (modal).
To use a State value as the trigger button label, wrap it in rx.text.
on_open_change passes the new open value (bool). Control it with open and a handler can close the panel.
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.