Skip to content

add CopyButton#662

Merged
AnnMarieW merged 7 commits intosnehilvj:masterfrom
AnnMarieW:add-copy-button
Nov 3, 2025
Merged

add CopyButton#662
AnnMarieW merged 7 commits intosnehilvj:masterfrom
AnnMarieW:add-copy-button

Conversation

@AnnMarieW
Copy link
Copy Markdown
Collaborator

@AnnMarieW AnnMarieW commented Oct 22, 2025

Add Copy-to-Clipboard Components

This PR adds two components for creating copy-to-clipboard buttons with Mantine styles.

1. CopyButton

A ready-to-use copy button built with the Mantine Button component. Supports props for dynamic text, icons and colors while copying: children, copiedChildren, color, copiedColor, plus standard Button props (size, radius, variant, etc.).

2. CustomCopyButton

Provides full flexibility to define custom copy button behavior and styling using JavaScript.
Built with the functions as props pattern.

CopyButton Examples

import dash_mantine_components as dmc
from dash import Dash, Input, Output, no_update
from dash_iconify import DashIconify

app = Dash()

component = dmc.CopyButton(
    value="https://mantine.dev",
    children=DashIconify(icon="tabler:clipboard"),
    copiedChildren=DashIconify(icon="tabler:check"),
    color="blue",
    copiedColor="teal",
    n_clicks=0,
    id="copy"
)

app.layout = dmc.MantineProvider(dmc.Box([
        dmc.Text("CopyButton demo"),

        dmc.Text("Basic copy button", mt="lg"),
        component,
        dmc.Text("Click the button below to trigger a callback that updates the value "
                "and automatically copies it to the clipboard", mt="md"),
        dmc.Button("update copy",  n_clicks=0, id="update"),
    ], m="lg")
)


@app.callback(
    Output("copy", "value"),
    Output("copy", "triggerCopy"),
    Input("update", "n_clicks"),
)
def update(n):
    if n > 0:
        return str(n) + "update", True
    return no_update, no_update

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

CustomCopyButton Example

This example uses an ActionIcon and a Tooltip as the copy button

import dash_mantine_components as dmc
from dash import Dash
from dash_iconify import DashIconify

app = Dash()

custom_copy = dmc.CustomCopyButton(
    value="Custom Copy demo",
    children={"function": "copyActionIcon"},

)

app.layout = dmc.MantineProvider(dmc.Box([
        dmc.Text(
            "This example uses CustomCopyButton with a JavaScript function to create "
            "a custom ActionIcon with Tooltip",
            mt="md"),
        custom_copy

    ], m="lg")
)


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

defined in a .js file in /assets:

var dmcfuncs = window.dashMantineFunctions = window.dashMantineFunctions || {};
var dmc = window.dash_mantine_components;
var DashIconify = window.dash_iconify.DashIconify;

dmcfuncs.copyActionIcon = function({ copied, copy }) {
  return React.createElement(
    dmc.Tooltip,
    {
      label: copied ? 'Copied' : 'Copy',
      withArrow: true,
      position: 'right'
    },
    React.createElement(
      dmc.ActionIcon,
      {
        color: copied ? 'teal' : 'gray',
        variant: 'subtle',
        onClick: copy
      },
      copied
        ? React.createElement(DashIconify, { icon: 'tabler:check', style: { width: 16, height: 16 } })
        : React.createElement(DashIconify, { icon: 'tabler:copy', style: { width: 16, height: 16 } })
    )
  );
};

@AnnMarieW AnnMarieW marked this pull request as ready for review October 23, 2025 17:39
@alexcjohnson
Copy link
Copy Markdown
Collaborator

Your second - and third I guess - copy-to-clipboard components plotly/dash-core-components#932 -> https://dash.plotly.com/dash-core-components/clipboard 😎

These ones have a little different API than the one in DCC. Which I guess flows from the way Mantine does things, but I'm worried that this way is harder to use in a Dash context (particularly for the callback-updated-value use case - is it possible to do this within the one component or does it have to be a separate button like in your example? If not, what would you do if you ONLY want the callback value, hide the CopyButton?), also less capable in that it can't read from another element directly (so if that's what you want you have to duplicate the content and keep it synchronized) and it doesn't support copying as HTML.

Changing to a pattern more like dcc.Clipboard would take the API farther from Mantine's CopyButton, but if it can solve these issues perhaps that's a worthwhile trade?

@AnnMarieW
Copy link
Copy Markdown
Collaborator Author

AnnMarieW commented Oct 28, 2025

Haha - that link to the dcc.Clipboard PR was a trip down memory lane! That was one of my first contributions to Dash. And thanks for the great review way back then too! 🙂

Diverging with the Mantine API is ok if it's for parity with Dash. I think the feature of being able to specify a target_id to copy the text of a different component without using a callback is pretty slick. I can move that logic over.

The Mantine CopyButton uses the the use-clipboard hook which does not support html, so I'll defer that feature. People can use the dcc.Clipboard if they need to copy html for now.

It's possible to update the value in a single callback as in the example below. The value updates first, so it works. It uses the triggerCopy prop to trigger the copy manually. (I like this better than updating the n_clicks - like in dcc.Clipboard is it too late to fix the Clipoard PR? 😛 )

import dash_mantine_components as dmc
from dash import Dash, Input, Output, no_update
from dash_iconify import DashIconify

app = Dash()

component = dmc.CopyButton(
    value="",
    children=DashIconify(icon="tabler:clipboard"),
    copiedChildren=DashIconify(icon="tabler:check"),
    color="blue",
    copiedColor="teal",
    n_clicks=0,
    id="copy"
)

app.layout = dmc.MantineProvider(dmc.Box([
        dmc.Text("CopyButton demo"),
        component,        
    ], m="lg")
)


@app.callback(
    Output("copy", "value"),
    Output("copy", "triggerCopy"),
    Input("copy", "n_clicks"),
)
def update(n):
    if n > 0:
        return str(n) + "update", True
    return no_update, no_update

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

@AnnMarieW
Copy link
Copy Markdown
Collaborator Author

AnnMarieW commented Oct 28, 2025

Oops. Looks like I spoke too soon. I tried adding the target_id prop like dcc.Clipboard. It doesn't work well with the Mantine use-clipboard hook. The value doesn't update before the copy() is called making it necessary to click the copy button twice to copy the correct value. I tried forcing a re-render, but couldn't make that work either.

I think this is still good to go as-is and I can open a feature request for the target_id prop. Even though it requires a callback to update the value dynamically, people may chose to use these components instead of dcc.Clipboard when they want to style the copy button with a Mantine theme or customize it using other components or icons.

Here is an example of copying the contents of a dmc.Textarea to the clipboard

import dash_mantine_components as dmc
from dash import Dash, Input, Output, State, no_update
from dash_iconify import DashIconify

app = Dash()

component = dmc.CopyButton(
    value="",
    children=DashIconify(icon="tabler:clipboard"),
    copiedChildren=DashIconify(icon="tabler:check"),
    color="blue",
    copiedColor="teal",
    n_clicks=0,
    id="copy"
)

app.layout = dmc.MantineProvider(dmc.Box([
        dmc.Text("CopyButton demo"),
        component,
        dmc.Textarea(id="textarea")
    ], m="lg")
)


@app.callback(
    Output("copy", "value"),
    Output("copy", "triggerCopy"),
    Input("copy", "n_clicks"),
    State("textarea", "value")
)
def update(n, val):
    if n > 0:
        return val, True
    return no_update, no_update

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

@AnnMarieW
Copy link
Copy Markdown
Collaborator Author

Well, might have found a solution for the target_id. This works correctly now:

import dash_mantine_components as dmc
from dash import Dash
from dash_iconify import DashIconify

app = Dash()

component = dmc.CopyButton(
    value="",
    children=DashIconify(icon="tabler:clipboard"),
    copiedChildren=DashIconify(icon="tabler:check"),
    color="blue",
    copiedColor="teal",
    n_clicks=0,
    id="copy",
    target_id="textarea"
)

app.layout = dmc.MantineProvider(dmc.Box([
        dmc.Text("CopyButton demo"),
        component,
        dmc.Textarea(id="textarea")
    ], m="lg")
)


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

Copy link
Copy Markdown
Collaborator

@alexcjohnson alexcjohnson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever solution to get target_id to work 🎉

@AnnMarieW AnnMarieW merged commit d3da52c into snehilvj:master Nov 3, 2025
1 check passed
@AnnMarieW AnnMarieW deleted the add-copy-button branch November 3, 2025 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants