New Component: st-copy, a new way to copy anything

Hi Streamlit Community! :waving_hand:

I’m excited to introduce st-copy, a new Streamlit component that adds a customizable “copy to clipboard” button to your apps.

:magnifying_glass_tilted_left: What is st-copy?

st-copy allows you to add a copy button to any text content in your Streamlit app. It’s especially useful for copying code snippets, messages, or any other text, enhancing user interaction.

:sparkles: Features

  • Streamlit theme aware: Adapts icon colour & tooltip style automatically; works in both light and dark themes.
  • Two icon styles: Google Material Symbols (default) or the native Streamlit code‑block icon.
  • Custom tooltip & “Copied!” label: Localised UI in one line.
  • Keyboard‑friendly: Fully focusable, press Enter/Space to copy.

:rocket: Installation

pip install st-copy

:test_tube: Example Usage

from st_copy import copy_button

copy_button(
    "Text to copy",
    tooltip="Copy this text",
    copied_label="Copied!",
    icon="st",
)

:television: Live Demo

Check out the live demo app here: st-copy.streamlit.app

:package: Source Code

Explore the source code on GitHub: alex-feel/st-copy


Feel free to try it out and let me know your thoughts or any suggestions for improvement!

6 Likes

This is great!

1 Like

I have created another solution that does not require React.
It is based on the iframe implementation found here: It Works! Custom "Copy to Clipboard" Buttons 😎 with the only difference that it uses srcdoc instead of src so it does not require serving a static html page from the same domain.
Add this function to the server code:

def get_copy_to_clipboard_iframe_html(self, text_to_copy: str) -> str:
        """
        Returns HTML for an iframe with a copy-to-clipboard button, using srcdoc.
        The text to copy is embedded directly in the HTML.
        Args:
            text_to_copy: The string to be copied to clipboard.
        Returns:
            HTML string for use as iframe srcdoc.
        """
        import html
        # Escape for HTML attribute and JS string
        safe_text = html.escape(text_to_copy, quote=True).replace("'", "'")
        # Also escape for JS string (double quotes)
        safe_text_js = safe_text.replace('"', '\\"')
        return f'''
            <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;
                        padding: 10px;
                        font-size: 16px;
                        border-radius: 8px;
                        background-color: #f0f0f0;
                        border: 1px solid #ccc;
                        cursor: pointer;
                        font-family: "Source Sans Pro", sans-serif;
                    }}
                    #copyButton:hover {{
                        background-color: #e0e0e0;
                    }}
                </style>
            </head>
            <body>
                <input type=\"text\" id=\"textToCopy\" value=\"{safe_text}\" style=\"position: absolute; left: -9999px;\">
                <button id=\"copyButton\" onclick=\"copyToClipboard()\">📋 Copy to clipboard</button>
                <script>
                    const textToCopy = document.getElementById('textToCopy');
                    const copyButton = document.getElementById('copyButton');
                    copyButton.title = textToCopy.value;
                    function copyToClipboard() {{
                        textToCopy.select();
                        document.execCommand('copy');
                        copyButton.textContent = '✔';
                        setTimeout(function () {{
                            copyButton.textContent = '📋';
                            copyButton.title = textToCopy.value;
                        }}, 1000);
                    }}
                </script>
            </body>
            </html>
            ''' 

and wherever you want to use it put:

# Render iframe with copy-to-clipboard button using srcdoc
            iframe_html = self.get_copy_to_clipboard_iframe_html(text_output)
            iframe_srcdoc = iframe_html.replace('"', '&quot;').replace('\n', ' ')
            st.markdown(
                f'<iframe srcdoc="{iframe_srcdoc}" width="200" height="60" style="border:none;"></iframe>',
                unsafe_allow_html=True
            )

Of course, you may use different styling.

1 Like

Really good job! The formatting of the output is also super nice. For future works, would it be possible to also have a option for a text button for the target groups, who are not so familiar with icons?