R

ReflexKR

Documentación de Reflex

El taller de componentes: los 100 componentes de Reflex

·

Resumen del curso

Diseño

·

Box

·

Flex

·

Stack

·

Grid

·

Container

·

Section

·

Center

·

Spacer

·

Separator

·

Card

·

Inset

·

Aspect Ratio

·

Fragment

Tipografía

·

Text

·

Heading

·

Link

·

Code

·

Markdown

·

Blockquote

·

Quote

·

Strong

·

Em

·

Kbd

Formularios

·

Button

·

Input

·

Text Area

·

Checkbox

·

Switch

·

Radio Group

·

Select

·

Slider

·

Form

·

Upload

Visualización de datos

·

Badge

·

Avatar

·

Icon

·

Callout

·

List

·

Data List

·

Code Block

·

Progress

·

Spinner

·

Scroll Area

·

Moment

Mostrar y ocultar

·

Tabs

·

Accordion

·

Segmented Control

Superposiciones

·

Tooltip

·

Popover

·

Hover Card

·

Dialog

·

Alert Dialog

·

Drawer

·

Dropdown Menu

·

Context Menu

·

Toast

Renderizado dinámico

·

Cond

·

Match

·

Foreach

·

Auto Scroll

Multimedia

·

Image

·

Audio

·

Video

Tablas y cuadrículas de datos

·

Table

·

Data Table

·

Data Editor

Gráficos

·

Area Chart

·

Bar Chart

·

Line Chart

·

Composed Chart

·

Pie Chart

·

Radar Chart

·

Radial Bar Chart

·

Scatter Chart

·

Funnel Chart

·

Treemap

·

Sankey Chart

·

Error Bar

Piezas de gráficos

·

Axis

·

Cartesian Grid

·

Chart Tooltip

·

Legend

·

Label

·

Reference

·

Brush

Otras bibliotecas de gráficos

·

Plotly

·

Pyplot

Elementos HTML

·

HTML

·

HTML Text

·

HTML Layout

·

HTML Forms

·

HTML Media

·

HTML Tables

·

SVG

Otras herramientas

·

Theme

·

Skeleton

·

Clipboard

·

Html Embed

·

Script

·

Memo

El taller de componentes: los 100 componentes de Reflex
Superposiciones

Context Menu

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_content

Documentación oficial

Pedido del taller

En la lista de archivos, al hacer clic derecho sobre el nombre de un archivo, abre ahí mismo un menú para marcarlo con estrella, duplicarlo o borrarlo.

Un menú contextual es un menú que aparece en la posición del ratón al hacer clic derecho sobre un elemento (en pantallas táctiles, al mantenerlo pulsado). Sirve para reunir lo que se puede hacer «con esto»: un archivo, una fila de una tabla, una imagen. Sus partes son las mismas que las del menú desplegable.

  • context_menu.root: envuelve todas las partes.
  • context_menu.trigger: envuelve el elemento que recibe el clic derecho.
  • context_menu.content: el panel que aparece al abrirse.
  • context_menu.item: una opción. Con shortcut se le añade el texto de un atajo.
  • context_menu.separator: una línea entre opciones.
  • context_menu.sub: envuelve un submenú. Dentro van context_menu.sub_trigger y 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"),
    ),
)

Prueba las propiedades

Haz clic derecho en el recuadro de puntos. El aspecto del panel va en content, y disabled, que impide el clic derecho, va en trigger. Con disabled activado aparece el menú contextual normal del navegador.

Prueba las propiedades

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,
)
El menú contextual se abre donde se pulsa el ratón, así que lo que más se cambia no es side·align sino variant·size·color_scheme.
PartePropiedadValoresUso
rootmodalTrue / FalseBloquear la interacción exterior mientras está abierto
rooton_open_changeEventoAl abrirse o cerrarse. Pasa el nuevo valor de apertura (bool)
triggerdisabledTrue / FalseQue no se abra con el clic derecho
contentvariant · size"solid" "soft" · "1" "2"Tipo de resaltado, tamaño
contentcolor_scheme · high_contrastNombre de color · boolColor con que se pintan las opciones, contraste
contentloop · side · align · side_offset · align_offset-Recorrido circular con el teclado, ajuste fino de la posición
contentavoid_collisions · collision_padding · sticky · hide_when_detached-Comportamiento en el borde de la pantalla
itemshortcut · color_scheme · disabled · text_value-Texto de atajo, color, impedir elegirla, texto para buscar tecleando
itemon_selectEventoAl elegir la opción
subopen · default_open · on_open_change-Apertura del submenú
sub_triggerdisabled · text_value-La opción que abre el submenú
sub_contentloop · side_offset · align_offset, etc.-Posición del panel secundario

A diferencia del menú desplegable, root no tiene open: el menú solo se abre con el clic derecho. content y sub_content tienen los eventos on_escape_key_down, on_pointer_down_outside, on_focus_outside y on_interact_outside.

Las opciones, justo debajo de content

context_menu.item tiene que ser hijo directo de context_menu.content. Si, para cambiar las opciones según una condición, las agrupas en una caja como rx.vstack, el menú deja de reconocerlas. Esta es la forma que la documentación oficial prohíbe.

rx.context_menu.content(
    rx.cond(
        State.count % 2 == 0,
        rx.vstack(  # no las agrupes en una caja
            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"),
        ),
    )
)

Si quieres que las opciones cambien según el State, convierte la lista en una variable calculada y dibuja las opciones directamente con rx.foreach, que no crea ninguna caja extra. Abajo, pulsa el botón count y vuelve a hacer clic derecho.

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

Al abrirse y cerrarse: on_open_change

El on_open_change de context_menu.root pasa True cuando se abre y False cuando se cierra.

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

Abrir un diálogo desde el menú

Igual que con el menú desplegable, si metes un diálogo dentro de una opción, desaparece cuando el menú se cierra. Pon los diálogos fuera del menú y haz que las opciones solo cambien un valor del State. Cada diálogo se abre cuando ese valor coincide con su nombre, y al cerrarse on_open_change vacía el valor. Haz clic derecho en el icono de abajo.

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

Completar el pedido

Cada fila de archivo se dibuja con rx.foreach y va envuelta en su propio menú contextual. El on_select de cada opción recibe el nombre de ese archivo, de modo que un solo handler funciona en cualquier fila.

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

Notas del taller

  • Se abre con clic derecho (o pulsación larga). Las partes son las del menú desplegable: root · trigger · content · item · separator · sub.

  • item debe ser hijo directo de content. Las opciones dinámicas se dibujan directamente con rx.foreach.

  • root no tiene open. Para saber si está abierto, usa on_open_change.

  • Con disabled=True en trigger aparece el menú normal del navegador.

  • El clic derecho es una función invisible. Ofrece las mismas acciones también con botones o un menú desplegable.

← Anterior

Superposiciones · Dropdown Menu

Siguiente →

Superposiciones · Toast

Recibe avisos de nuevas lecciones

Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.

Datos: tu correo electrónico · Finalidad: avisos de nuevas lecciones o de la publicación de este curso · Conservación: hasta que pidas que lo borremos. No lo usamos para nada más ni lo compartimos. Responde a cualquier aviso y lo borraremos enseguida.

Preguntas y respuestas

Pregunta lo que quieras sobre esta lección. Aquí se reúnen las preguntas de todas las versiones de idioma de esta página.

Se muestra como texto plano, sin enlaces ni formato.

R

ReflexKR

Aprende Python y Reflex haciendo. Todos los ejemplos se ejecutaron y verificaron en Reflex 0.10.0.

ReflexKR es gratis y sin banners publicitarios. Si te ayudó, puedes apoyarlo con un café. ☕ Invítanos un café

Aviso de afiliados
한국어English日本語简体中文EspañolDeutsch

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

Built with Reflex