If you publish snippets in WordPress, you probably want a Copy button on every core Code block. Readers can then grab the code without dragging a selection across the lines and missing the last one. Core doesn’t include that button, and most plugins that add one also bring in a syntax highlighter you may not want.
This small plugin adds a Copy button to the top-right corner of every core Code block (pre.wp-block-code) on the front end. When clicked, the button copies the code, changes to Copied! for two seconds and then goes back to Copy. It uses vanilla JavaScript and the asynchronous Clipboard API, with no jQuery. The JS and CSS only load on pages that actually render a Code block. I assumed you want the button on the front end only, not in the editor.
The snippet
Create a folder wp-content/plugins/dpc-code-copy/ containing three files. The first one is the PHP loader, dpc-code-copy.php:
<?php
/**
* Plugin Name: DPC Code Block Copy Button
* Description: Adds a Copy button to every core Code block on the front end.
* Version: 1.0.0
*/
defined( 'ABSPATH' ) || exit;
const DPC_CODE_COPY_VERSION = '1.0.0';
add_action( 'init', 'dpc_code_copy_register_assets' );
function dpc_code_copy_register_assets(): void {
wp_register_script(
'dpc-code-copy',
plugins_url( 'copy.js', __FILE__ ),
array(),
DPC_CODE_COPY_VERSION,
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
wp_add_inline_script(
'dpc-code-copy',
'window.dpcCodeCopy = ' . wp_json_encode(
array(
'copy' => __( 'Copy', 'dpc-code-copy' ),
'copied' => __( 'Copied!', 'dpc-code-copy' ),
'failed' => __( 'Copy failed', 'dpc-code-copy' ),
)
) . ';',
'before'
);
wp_register_style(
'dpc-code-copy',
plugins_url( 'copy.css', __FILE__ ),
array(),
DPC_CODE_COPY_VERSION
);
}
// Enqueue only when a core Code block is actually rendered.
add_filter( 'render_block_core/code', 'dpc_code_copy_enqueue_on_render' );
function dpc_code_copy_enqueue_on_render( string $block_content ): string {
if ( ! is_admin() ) {
wp_enqueue_script( 'dpc-code-copy' );
wp_enqueue_style( 'dpc-code-copy' );
}
return $block_content;
}
Next, copy.js:
( () => {
const labels = Object.assign(
{ copy: 'Copy', copied: 'Copied!', failed: 'Copy failed' },
window.dpcCodeCopy || {}
);
// The Clipboard API only exists on HTTPS (and localhost).
if ( ! window.isSecureContext || ! navigator.clipboard ) {
return;
}
document.querySelectorAll( 'pre.wp-block-code' ).forEach( ( pre ) => {
const code = pre.querySelector( 'code' );
if ( ! code || pre.querySelector( ':scope > .dpc-code-copy' ) ) {
return;
}
const button = document.createElement( 'button' );
button.type = 'button';
button.className = 'dpc-code-copy';
button.textContent = labels.copy;
button.setAttribute( 'aria-live', 'polite' );
let timer;
button.addEventListener( 'click', async () => {
try {
await navigator.clipboard.writeText( code.innerText );
button.textContent = labels.copied;
} catch {
button.textContent = labels.failed;
}
clearTimeout( timer );
timer = setTimeout( () => {
button.textContent = labels.copy;
}, 2000 );
} );
pre.classList.add( 'dpc-has-copy' );
pre.append( button );
} );
} )();
Finally, copy.css:
.wp-block-code.dpc-has-copy {
position: relative;
}
.wp-block-code.dpc-has-copy > code {
padding-right: 5rem;
}
.dpc-code-copy {
position: absolute;
top: 0.5rem;
right: 0.5rem;
padding: 0.25rem 0.6rem;
font-family: system-ui, sans-serif;
font-size: 0.75rem;
line-height: 1.4;
color: inherit;
background: color-mix(in srgb, currentColor 10%, transparent);
border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
border-radius: 4px;
cursor: pointer;
opacity: 0.75;
}
.dpc-code-copy:hover,
.dpc-code-copy:focus-visible {
opacity: 1;
}
Activate DPC Code Block Copy Button under Plugins. You can also drop all three files into wp-content/mu-plugins/, but then you need a loader file there, because WordPress doesn’t scan subfolders of mu-plugins.
How it works
- The script and style are registered on
init, not onwp_enqueue_scripts. A block theme renders its template beforewp_head()runs, so the handles have to exist before any block renders. render_block_core/coderuns once for each Code block. Enqueuing there means pages without code load no extra files. In a block theme the stylesheet still prints in the<head>. The pattern is the same as in Load script only when you need it.- The labels go to JS through
wp_json_encode()in abeforeinline script. They are translatable and safely encoded, and thedeferstrategy stays intact. - The script finds every
pre.wp-block-code, adds one button to each, and copiescode.innerText.innerTextturns old-style<br>line breaks into real newlines. - One timer per button resets the label after 2000 ms. Clicking again during that time restarts the two seconds instead of stacking timers.
aria-live="polite"makes screen readers announce the change to “Copied!”.
Customise it
- Change the wording in the
__()calls, or translate it with thedpc-code-copytext domain. - Change
2000incopy.jsto change how long “Copied!” stays visible. - To show the button only on hover, set
opacity: 0on.dpc-code-copyand add.wp-block-code:hover .dpc-code-copy { opacity: 1; }. Keep the:focus-visiblerule so keyboard users can still see it. - To restyle the button with your theme colours, swap
currentColorfor preset variables such asvar(--wp--preset--color--contrast).
Gotchas
- The Clipboard API only works over HTTPS or on
localhost. On plain HTTP the script adds no buttons, so you never get a button that can’t do anything. - The core Code block wraps long lines by default. If your theme switches to
white-space: prewith horizontal scrolling, the button scrolls along with the content because it sits inside thepre. - Buttons are only added to code that exists when the page loads. Code inserted later by AJAX or infinite scroll needs the loop wrapped in a function you call again.
- An older post on this site, Copy code to clipboard, covers a general copy button. This one targets the core Code block and loads its files only when needed.
If you’d rather not create a plugin folder, Scripts Organizer lets you add the JS and CSS as snippets from inside WordPress.