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.context_menu.rootrx.context_menu.triggerrx.context_menu.contentrx.context_menu.itemrx.context_menu.separatorrx.context_menu.subrx.context_menu.sub_triggerrx.context_menu.sub_contentOfficial docs
Workshop order
When someone right-clicks a file name in the file list, open a menu right there for starring, duplicating or deleting it.
A context menu opens at the pointer when you right-click an element (or long-press it on a touch screen). Use it to collect the things you can do "with this one", such as a file, a table row or an image. Its parts are the same as the dropdown menu's.
context_menu.root: wraps all the parts.context_menu.trigger: wraps the element that receives the right-click.context_menu.content: the panel that opens.context_menu.item: a single item. shortcut adds shortcut text.context_menu.separator: a line between items.context_menu.sub: wraps a submenu. Inside it go context_menu.sub_trigger and context_menu.sub_content.rx.context_menu.root(
rx.context_menu.trigger(
rx.button("Right click me"),
),
rx.context_menu.content(
rx.context_menu.item("Edit", shortcut="⌘ E"),
rx.context_menu.item("Duplicate", shortcut="⌘ D"),
rx.context_menu.separator(),
rx.context_menu.item("Archive", shortcut="⌘ N"),
rx.context_menu.sub(
rx.context_menu.sub_trigger("More"),
rx.context_menu.sub_content(
rx.context_menu.item("Move to project…"),
rx.context_menu.item("Move to folder…"),
rx.context_menu.separator(),
rx.context_menu.item("Advanced options…"),
),
),
rx.context_menu.separator(),
rx.context_menu.item("Share"),
rx.context_menu.item("Add to favorites"),
rx.context_menu.separator(),
rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"),
),
)Right-click the dashed box. The look of the menu panel goes on content, and disabled, which blocks the right-click, goes on trigger. With disabled on, the browser's own right-click menu appears instead.
Try the props
Right-click here
variant
size
color
high_contrast
disabled
modal
rx.context_menu.root(
rx.context_menu.trigger(
rx.center(
rx.text("Right-click here", size="2", color_scheme="gray"),
width="260px",
height="110px",
border=f"2px dashed {rx.color('gray', 7)}",
border_radius="8px",
),
disabled=False,
),
rx.context_menu.content(
rx.context_menu.item("Copy", shortcut="⌘ C"),
rx.context_menu.item("Paste", shortcut="⌘ V"),
rx.context_menu.separator(),
rx.context_menu.item("Delete", shortcut="⌘ ⌫", color_scheme="red"),
variant="solid",
size="2",
color_scheme="indigo",
high_contrast=False,
),
modal=True,
)| Part | Prop | Values | Use |
|---|---|---|---|
root | modal | True / False | Block interaction outside while open |
root | on_open_change | event | Fires on open or close; passes the new open value (bool) |
trigger | disabled | True / False | Don't open on right-click |
content | variant · size | "solid" "soft" · "1" "2" | Highlight style, size |
content | color_scheme · high_contrast | color name · bool | Color used for items, contrast |
content | loop · side · align · side_offset · align_offset | - | Keyboard wrap-around, fine-tuning the position |
content | avoid_collisions · collision_padding · sticky · hide_when_detached | - | Screen-edge handling |
item | shortcut · color_scheme · disabled · text_value | - | Shortcut text, color, make it unselectable, text for typeahead |
item | on_select | event | An item is chosen |
sub | open · default_open · on_open_change | - | Open state of the submenu |
sub_trigger | disabled · text_value | - | The item that opens the submenu |
sub_content | loop · side_offset · align_offset etc. | - | Placement of the submenu panel |
Unlike the dropdown menu, root has no open: the menu only opens on right-click. content and sub_content have the on_escape_key_down, on_pointer_down_outside, on_focus_outside and on_interact_outside events.
context_menu.item must be a direct child of context_menu.content. If you group items in a box such as rx.vstack to switch them on a condition, the menu no longer recognizes them. This is the form the official docs warn against.
rx.context_menu.content(
rx.cond(
State.count % 2 == 0,
rx.vstack( # don't wrap items in a box
rx.context_menu.item("Even Option 1"),
rx.context_menu.item("Even Option 2"),
),
rx.vstack(
rx.context_menu.item("Odd Option A"),
rx.context_menu.item("Odd Option B"),
),
)
)To change the items based on State, make the list a computed var and draw the items directly with rx.foreach, which does not add a box of its own. Click the count button below, then right-click again.
Selected: none
class ContextMenuOptionsState(rx.State):
count: int = 0
selected: str = "none"
@rx.event
def increment(self):
self.count += 1
@rx.event
def set_selected(self, value: str):
self.selected = value
@rx.var
def options(self) -> list[str]:
if self.count % 2 == 0:
return ["Even Option 1", "Even Option 2", "Even Option 3"]
return ["Odd Option A", "Odd Option B", "Odd Option C"]
rx.flex(
rx.context_menu.root(
rx.context_menu.trigger(rx.button("Right click me")),
rx.context_menu.content(
rx.foreach(
ContextMenuOptionsState.options,
lambda option: rx.context_menu.item(option, on_select=ContextMenuOptionsState.set_selected(option)),
),
),
),
rx.button("count = ", ContextMenuOptionsState.count, variant="soft", on_click=ContextMenuOptionsState.increment),
rx.text("Selected: ", ContextMenuOptionsState.selected, size="2"),
spacing="3",
align="center",
wrap="wrap",
)The on_open_change of context_menu.root passes True when it opens and False when it closes.
Opened or closed: 0 times
Context menu open: no
class ContextMenuEventState(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: ", ContextMenuEventState.num_opens, " times"),
rx.text("Context menu open: ", rx.cond(ContextMenuEventState.opened, "yes", "no")),
rx.context_menu.root(
rx.context_menu.trigger(rx.button("Right click me", variant="soft")),
rx.context_menu.content(
rx.context_menu.item("Copy"),
rx.context_menu.item("Paste"),
),
on_open_change=ContextMenuEventState.count_opens,
),
direction="column",
spacing="3",
align="start",
)As with the dropdown menu, a dialog placed inside a menu item disappears when the menu closes. Keep the dialogs outside the menu and let the items change only a State value. Each dialog opens when that value is its own name, and on_open_change clears the value when it closes. Right-click the icon below.
class ContextMenuDialogState(rx.State):
which_dialog_open: str = ""
@rx.event
def set_which_dialog_open(self, value: str):
self.which_dialog_open = value
@rx.event
def delete(self):
yield rx.toast("Deleted item")
@rx.event
def save_settings(self):
yield rx.toast("Saved settings")
rx.vstack(
rx.context_menu.root(
rx.context_menu.trigger(rx.icon_button(rx.icon("ellipsis-vertical"), variant="soft", aria_label="Right-click for menu")),
rx.context_menu.content(
rx.context_menu.item("Delete", on_click=ContextMenuDialogState.set_which_dialog_open("delete")),
rx.context_menu.item("Settings", on_click=ContextMenuDialogState.set_which_dialog_open("settings")),
),
),
rx.cond(
ContextMenuDialogState.which_dialog_open,
rx.text(ContextMenuDialogState.which_dialog_open, " dialog is open"),
),
rx.alert_dialog.root(
rx.alert_dialog.content(
rx.alert_dialog.title("Are you Sure?"),
rx.alert_dialog.description(
"This action cannot be undone. Are you sure you want to delete this item?",
margin_bottom="20px",
),
rx.hstack(
rx.alert_dialog.action(
rx.button("Delete", color_scheme="red", on_click=ContextMenuDialogState.delete),
),
rx.spacer(),
rx.alert_dialog.cancel(rx.button("Cancel")),
),
),
open=ContextMenuDialogState.which_dialog_open == "delete",
on_open_change=ContextMenuDialogState.set_which_dialog_open(""),
),
rx.dialog.root(
rx.dialog.content(
rx.dialog.title("Settings"),
rx.dialog.description("Set your settings in this settings dialog.", margin_bottom="20px"),
rx.dialog.close(
rx.button("Close", on_click=ContextMenuDialogState.save_settings),
),
),
open=ContextMenuDialogState.which_dialog_open == "settings",
on_open_change=ContextMenuDialogState.set_which_dialog_open(""),
),
align="center",
)Draw each file row with rx.foreach and wrap every row in a context menu. Each item's on_select passes that file's name, so a single handler works from any row.
notes.txt
budget.xlsx
photo.png
class ContextMenuFileState(rx.State):
files: list[str] = ["notes.txt", "budget.xlsx", "photo.png"]
starred: list[str] = []
@rx.event
def toggle_star(self, name: str):
if name in self.starred:
self.starred = [f for f in self.starred if f != name]
else:
self.starred = self.starred + [name]
@rx.event
def duplicate(self, name: str):
stem, dot, ext = name.rpartition(".")
self.files = self.files + [f"{stem} copy{dot}{ext}"]
@rx.event
def remove(self, name: str):
self.files = [f for f in self.files if f != name]
self.starred = [f for f in self.starred if f != name]
@rx.event
def reset_files(self):
self.files = ["notes.txt", "budget.xlsx", "photo.png"]
self.starred = []
rx.vstack(
rx.foreach(
ContextMenuFileState.files,
lambda name: rx.context_menu.root(
rx.context_menu.trigger(
rx.flex(
rx.icon("file"),
rx.text(name),
rx.spacer(),
rx.cond(ContextMenuFileState.starred.contains(name), rx.icon("star", color=rx.color("amber", 9))),
spacing="2",
align="center",
width="100%",
padding="6px 10px",
border_radius="6px",
_hover={"background": rx.color("gray", 3)},
),
),
rx.context_menu.content(
rx.context_menu.item("Star / unstar", on_select=ContextMenuFileState.toggle_star(name)),
rx.context_menu.item("Duplicate", shortcut="⌘ D", on_select=ContextMenuFileState.duplicate(name)),
rx.context_menu.separator(),
rx.context_menu.item("Delete", shortcut="⌘ ⌫", color_scheme="red", on_select=ContextMenuFileState.remove(name)),
),
),
),
rx.button("Reset", size="1", variant="soft", on_click=ContextMenuFileState.reset_files),
width="100%",
max_width="320px",
spacing="1",
)Workshop notes
Opens on right-click (long-press). Same parts as the dropdown menu: root · trigger · content · item · separator · sub.
item must be a direct child of content. Draw dynamic items directly with rx.foreach.
root has no open. Use on_open_change if you need to know whether it is open.
With disabled=True on trigger, the browser's own menu appears.
Right-click is an invisible feature. Offer the same actions through a button or a dropdown menu as well.
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.