How to trigger html.Button in dcc.Markdown

Is there a way to put an html.Button in dcc.Markdown that can trigger a callback when it is clicked?

You can’t exactly put an ‘html.Button()’ exactly inside markdown, but you can get pretty close. You can make the markdown itself clickable surrounding it with a html.Div() and triggering a callback the div’s n_clicks property.

from dash import Dash, html, Input, Output, callback, dcc

app = Dash()

app.layout = html.Div(
    [
        html.Div(
            [dcc.Markdown(r"$\frac{clickable}{markdown}*\pi$", mathjax=True)],
            id="click-div",
            style={"color": "red", "font-weight": "bold"},
        ),
        html.P(id="click-output"),
    ]
)


@callback(
    Output("click-output", "children"),
    Input("click-div", "n_clicks")
    )
def click_counter(n_clicks):
    return f"The markdown above has been clicked this many times: {n_clicks}"


app.run(debug=True)

You could do something like this:

import dash
from dash import dcc, html, Input, Output, State

app = dash.Dash(__name__)

app.layout = html.Div(
    id="main_container",
    style={
        'maxWidth': '800px',
        'margin': 'auto',
        'padding': '20px',
        'backgroundColor': '#f9f9f9',
        'borderRadius': '12px',
        'boxShadow': '0 8px 16px rgba(0,0,0,0.2)',
    },
    children=[
        html.Div(
            children=[
                html.P(
                    "Button position relative to upper left corner of the rectangle",
                    style={'marginTop': '20px', 'textAlign': 'center'}
                ),
                dcc.Input(id="top",value=50, debounce=True, type="number"),
                dcc.Input(id="left",value=50, debounce=True, type="number"),
            ],
            style={'textAlign': 'center'}
        ),

        # --- Positioning Parent Container ---
        # This Div contains both the Markdown and the Button.
        # It MUST have 'position: relative' to serve as the reference point for the
        # absolutely positioned child (the button).
        html.Div(
            id='positioning-container',
            style={
                'position': 'relative',
                'height': '350px',  # Give the container a fixed height to see the effect
                'border': '3px solid red',
                'padding': '20px',
                'marginTop': '20px',
            },
            children=[
                # 1. dcc.Markdown component (The main content/context of the parent area)
                dcc.Markdown(
                    children=r"$\frac{20}{80}*\pi$",
                    mathjax=True,
                    style={
                        'color': '#4b5563',
                        'padding': '10px'
                    }
                ),

                # 2. html.Button component (positioned absolutely)
                html.Button(
                    'Floating Button',
                    id='btn',
                    style={
                        # IMPORTANT: Set position to 'absolute' to break it out of the normal flow
                        # and position it relative to the nearest 'position: relative' ancestor.
                        'position': 'absolute',
                        # Define the coordinates relative to the 'positioning-container'
                        'top': "50%",
                        'left': "50%",
                        "transform": "translate(-50%, -50%)",  # Centers the button on the coordinates
                        'zIndex': '10', # Ensure it sits on top
                        'transition': 'all 0.2s', # control sliding animation
                    },
                    n_clicks=0,
                )
            ]
        ),
        html.Div(
            id="out",
            style={'marginTop': '20px', 'textAlign': 'center'}
        )
    ]
)


@app.callback(
    Output("out", "children"),
    Input("btn", "n_clicks")
)
def show_clicks(clicks):
    return f"button clicked {clicks} times."

@app.callback(
    Output("btn", "style"),
    Input("top", "value"),
    Input("left", "value"),
    State("btn", "style")
)
def show_clicks(top, left, style:dict):
    style.update({"top": f"{top}%", "left": f"{left}%"})
    return style


if __name__ == '__main__':
    app.run(debug=True)

mred layout

2 Likes

Here’s yet another example - This one adds a dcc.Clipboard to the dcc.Markdown component and allows you to copy the content of the dcc.Markdown without even having to use a callback!

The example " Clipboard Icon inside a Scrollable Div" is on this page

3 Likes

Ha! Didn’t know about the clipboard component. It sure has been quite some while that I developed something serious in dash…

1 Like

Thanks for all of the suggestions. I will play around with them. Maybe I should explain my use case.

I have a very large app with many pages. In my header on each page I have a Help button. The help button triggers a modal with a markdown that I fill with different markdown help depending on the page the user is on. It works very nicely. I would like to embed in the help some short videos containing screen captures demonstrating the usage of the page. I was thinking of embedding multiple buttons within the help markdown that I could trigger on and open a model and show the specific video related to the help topic. I may have to just add a dropdown containing the videos related to the particular page under the markdown in the help modal. Then I could play the video the user selects in a modal or something. I just thought it would really be nice if the links or buttons could be imbedded directly in the help markdown right where the video topic is described.

Not sure if I understand this correctly, but if you render a different markdown for each help, you could add the button there?

Another thing that came to my mind:

The Dash Yadda might work well here!

Adding a dropdown to select the videos would be the easiest solution,

Another option is create the content with other dash components rather than using dcc.Markdown for full control over the layout and content.

1 Like

That’s what I was referring to, but you found better words for it :slight_smile:

1 Like

Hello @Brent,

If your heart is set on Markdown, you can add your own eventlistener to the Markdown when it is displayed.

You could capture the link that was clicked on and display it into a modal and stop the bubbling/propagation depending upon what was clicked.

1 Like

There is just one markdown. I just fill the contents of the markdown based on the page the user is on.

Yadda looks interesting.

I am exploring yada and looking at the examples.

yada = YadaAIO(yada_id=’my_yada’, yada_src=’assets/hdewig.png’)

gives the following error:

TypeError: YadaAIO.init() got an unexpected keyword argument ‘yada_src’

It looks like yada_src is now yada_sleep_src and yada_active_src. None of the documentation seems to reflect this.