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('"', '"').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.