It Works! Custom "Copy to Clipboard" Buttons 😎

This honestly felt a bit like dark magic -

Let’s go:

The trick here is that we can’t have Javascript when we want it - however we can have javascript if we use a separate html page embedded as an iframe.

First off, you’ll need to host this html file somewhere (as a side note, I couldn’t find way to host an html file directly using static file serving in Streamlit - I could point an iframe to the static file on my server and it would simply render the raw html content/source code into the frame. If there is a way to do this, please let me know!)

<html>
<head>
    <meta charset="UTF-8">
    <title>Copy Text to Clipboard</title>
    <meta http-equiv="Content-Security-Policy" content="frame-ancestors 'self' *">
    <style>
        #copyButton {
            transition: opacity 1s;
        }
    </style>
</head>
<body>
    <input type="text" id="textToCopy" value="" style="position: absolute; left: -9999px;">

    <button id="copyButton" onclick="copyToClipboard()">📋</button>

    <script>
        function getQueryParam(name) {
            const urlSearchParams = new URLSearchParams(window.location.search);
            return urlSearchParams.get(name);
        }

        const textToCopy = document.getElementById("textToCopy");
        const copyText = getQueryParam("copy");
        if (copyText) {
            textToCopy.value = decodeURIComponent(copyText);
        }

        const copyButton = document.getElementById("copyButton");
        copyButton.title = textToCopy.value;

        function copyToClipboard() {
            textToCopy.select();
            document.execCommand("copy");

            const clipboardContents = textToCopy.value;

            copyButton.textContent = "✔";
            setTimeout(function () {
                copyButton.textContent = "📋";
                copyButton.title = textToCopy.value;
            }, 1000);
        }
    </script>
</body>
</html>

Now back in Streamlit we can embed an iframe using st.markdown and then using the url argument “?copy=” we have direct access to adding data to the users clipboard :slight_smile:

import streamlit as st

text_to_copy = st.text_input("Hello, World!")

hosted_html_file = "https://everydayswag.org/files/copy.html"
iframe_url = f"{hosted_html_file}?copy={text_to_copy}"

st.markdown(f'<iframe src="{iframe_url}"></iframe>', unsafe_allow_html=True)

Now when you click on the “:clipboard:” button, the text_to_copy value will be copied to your clipboard. I also added a short transition that updates the button label so the user has some feedback that the function was activated.

This way we can pass any text we like directly to the html file using python f-strings served as an argument via url, and then any time the user clicks the button, we can add that text directly to the clipboard with Javascript via the html file.

Nice :ok_hand:

edit - I deployed a demo to the cloud but it doesn’t like embedding the html file from a different domain:

edit2 - github example