diff --git a/docs/src/conf.py b/docs/src/conf.py
index 354759a23..bde400535 100644
--- a/docs/src/conf.py
+++ b/docs/src/conf.py
@@ -149,6 +149,10 @@ html_favicon = 'static/favicon.ico'
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['static']
+# Custom CSS and JS files
+html_css_files = ['copy-button.css']
+html_js_files = ['copy-button.js']
+
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
diff --git a/docs/src/static/copy-button.css b/docs/src/static/copy-button.css
new file mode 100644
index 000000000..759f9a36f
--- /dev/null
+++ b/docs/src/static/copy-button.css
@@ -0,0 +1,108 @@
+.code-block-wrapper {
+ position: relative;
+ margin: 0;
+ display: block;
+}
+
+.copy-button {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.5rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.5rem;
+ background-color: rgba(255, 255, 255, 0.7);
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ border-radius: 0.375rem;
+ color: #6b7280;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ z-index: 10;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.code-block-wrapper:hover .copy-button,
+.highlight-default:hover .copy-button {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.copy-button:hover {
+ background-color: rgba(255, 255, 255, 0.95);
+ border-color: rgba(0, 0, 0, 0.2);
+ color: #374151;
+ transform: scale(1.1);
+}
+
+.copy-button:active {
+ transform: scale(1.05);
+}
+
+.copy-button:focus {
+ outline: 2px solid #3b82f6;
+ outline-offset: 2px;
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.copy-button:focus:not(:focus-visible) {
+ outline: none;
+}
+
+.copy-button svg {
+ width: 1rem;
+ height: 1rem;
+ display: block;
+}
+
+.copy-button.copied {
+ background-color: #10b981;
+ border-color: #10b981;
+ color: white;
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.copy-button.copied:hover {
+ background-color: #059669;
+ border-color: #059669;
+ transform: scale(1.1);
+}
+
+@media (prefers-color-scheme: dark) {
+ .copy-button {
+ background-color: rgba(55, 65, 81, 0.7);
+ border-color: rgba(255, 255, 255, 0.15);
+ color: #d1d5db;
+ }
+
+ .copy-button:hover {
+ background-color: rgba(55, 65, 81, 0.95);
+ border-color: rgba(255, 255, 255, 0.3);
+ color: #f3f4f6;
+ }
+}
+
+@media (max-width: 640px) {
+ .copy-button {
+ opacity: 1;
+ pointer-events: auto;
+ }
+}
+
+.code-block-wrapper div.highlight {
+ margin: 0;
+}
+
+.code-block-wrapper > .highlight-default,
+.code-block-wrapper > .highlight {
+ margin: 0;
+}
+
+div.highlight-default.notranslate,
+div.highlight-c.notranslate,
+div.highlight-python.notranslate {
+ position: relative;
+}
diff --git a/docs/src/static/copy-button.js b/docs/src/static/copy-button.js
new file mode 100644
index 000000000..b52da9f89
--- /dev/null
+++ b/docs/src/static/copy-button.js
@@ -0,0 +1,47 @@
+document.addEventListener('DOMContentLoaded', function() {
+ const codeBlocks = document.querySelectorAll('div.highlight');
+
+ codeBlocks.forEach(function(codeBlock) {
+ const parent = codeBlock.parentElement;
+
+ if (parent && parent.classList.contains('code-block-wrapper')) {
+ return;
+ }
+
+ const button = document.createElement('button');
+ button.className = 'copy-button';
+ button.type = 'button';
+ button.setAttribute('aria-label', 'Copy code to clipboard');
+ button.innerHTML = '';
+
+ button.addEventListener('click', function() {
+ const pre = codeBlock.querySelector('pre');
+ const code = pre ? pre.textContent : codeBlock.textContent;
+
+ navigator.clipboard.writeText(code).then(function() {
+ button.classList.add('copied');
+ button.innerHTML = '';
+ button.setAttribute('aria-label', 'Code copied to clipboard');
+
+ setTimeout(function() {
+ button.classList.remove('copied');
+ button.innerHTML = '';
+ button.setAttribute('aria-label', 'Copy code to clipboard');
+ }, 2000);
+ }).catch(function(err) {
+ console.error('Failed to copy code: ', err);
+ });
+ });
+
+ if (parent && (parent.classList.contains('highlight-default') || parent.classList.contains('highlight-c') || parent.classList.contains('highlight-python'))) {
+ parent.style.position = 'relative';
+ parent.appendChild(button);
+ } else {
+ const wrapper = document.createElement('div');
+ wrapper.className = 'code-block-wrapper';
+ codeBlock.parentNode.insertBefore(wrapper, codeBlock);
+ wrapper.appendChild(button);
+ wrapper.appendChild(codeBlock);
+ }
+ });
+});