Introduction
Boost your Gutenberg code blocks with just a simple snippet!
If you often share code on your WordPress site, adding line numbers and a copy button can make your content much easier to read and interact with. In this tutorial, I’ll show you how to enhance the default Gutenberg code block using a lightweight, easy-to-add PHP snippet without needing any plugins.
By default, the Gutenberg Code
block in WordPress is very basic — no line numbers and no easy way for visitors to copy the code.
In this tutorial. We will create a single code snippet that:
- Adds line numbers automatically to each code block.
- Inserts a sticky copy button styled like ChatGPT’s.
- Works in normal, wide and full-width alignments.
- Is responsive and uses clean and minimal CSS.
No extra plugins are required other than a code snippet plugin or a way to add code to your theme.
Step 1 – The Functional Snippet
This is the complete working snippet you paste into a Code Snippet plugin or your theme’s functions.php.
It includes all the CSS and JavaScript inline. There is no need for separate files.
add_action('wp_enqueue_scripts', function () {
/* CSS: Line numbers, copy button, alignment fix */
$css = <<<CSS
/* Wrapper around each code block */
.code-copy-wrapper {
position: relative;
margin-bottom: 1em;
}
/* Alignwide setting */
.code-copy-wrapper.alignwide {
max-width: var(--wp--style--global--content-size);
margin-left: auto;
margin-right: auto;
}
/* Alignfull setting */
.code-copy-wrapper.alignfull {
max-width: none;
width: 100%;
margin-left: 0;
margin-right: 0;
}
/* Main code block styling */
.code-copy-wrapper pre.wp-block-code {
position: relative;
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 5px;
padding: 2.7em 3.5em 1em 1em; /* right space for button, left for numbers */
white-space: pre-wrap;
word-wrap: break-word;
font-family: monospace;
counter-reset: linenumber;
margin: 0;
box-sizing: border-box;
}
/* Code inside block */
.code-copy-wrapper pre.wp-block-code code {
display: block;
white-space: pre-wrap;
word-wrap: break-word;
user-select: text;
}
/* Each line inside code */
.code-copy-wrapper pre.wp-block-code code > span {
display: block;
counter-increment: linenumber;
padding-left: 3.5em; /* space before code text */
position: relative;
}
/* Line number style */
.code-copy-wrapper pre.wp-block-code code > span::before {
content: counter(linenumber);
position: absolute;
left: 0;
width: 2.2em;
text-align: right;
padding-right: 0.5em;
color: #999;
user-select: none;
}
/* Copy button styling */
.code-copy-btn {
position: absolute;
top: 8px;
right: 8px;
background: transparent;
border: none;
cursor: pointer;
opacity: 0.7;
transition: opacity 0.3s ease;
z-index: 10;
padding: 0;
font-size: 14px;
color: #333;
display: flex;
align-items: center;
gap: 0.25em;
}
.code-copy-btn:hover,
.code-copy-btn:focus { opacity: 1; outline: none; }
CSS;
wp_register_script('code-enhancer', '', [], null, true);
wp_enqueue_script('code-enhancer');
wp_add_inline_style('wp-block-library', $css);
/* JS: Add copy button + wrap lines */
$js = <<<JS
document.addEventListener('DOMContentLoaded', function () {
const clipboardSVG = '<svg aria-hidden="true" focusable="false" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
const checkmarkSVG = '<svg aria-hidden="true" focusable="false" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
document.querySelectorAll('pre.wp-block-code').forEach(pre => {
if (pre.querySelector('.code-copy-btn')) return;
// Wrap with alignment support
if (!pre.parentNode.classList.contains('code-copy-wrapper')) {
const wrapper = document.createElement('div');
wrapper.className = 'code-copy-wrapper';
if (pre.classList.contains('alignwide')) wrapper.classList.add('alignwide');
if (pre.classList.contains('alignfull')) wrapper.classList.add('alignfull');
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
}
// Add copy button
const button = document.createElement('button');
button.className = 'code-copy-btn';
button.type = 'button';
button.setAttribute('aria-label', 'Copy code to clipboard');
button.innerHTML = clipboardSVG + ' Copy';
pre.appendChild(button);
// Wrap each line in a span for numbering
const code = pre.querySelector('code');
if (code && !code.dataset.linesWrapped) {
const lines = code.innerText.split('\\n');
const escapeHtml = text => text.replace(/[&<>"']/g, m => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
}[m] || m));
code.innerHTML = lines.map(line =>
'<span>' + (line === '' ? '​' : escapeHtml(line)) + '</span>'
).join('');
code.dataset.linesWrapped = 'true';
}
// Copy to clipboard
button.addEventListener('click', () => {
if (!code) return;
navigator.clipboard.writeText(code.innerText).then(() => {
button.innerHTML = checkmarkSVG + ' Copied';
setTimeout(() => button.innerHTML = clipboardSVG + ' Copy', 2000);
});
});
});
});
JS;
wp_add_inline_script('code-enhancer', $js);
});
Step 2 – How it works
- CSS styles the wrapper, line numbers, and copy button.
- JavaScript:
- Wraps each
<pre>
in a container for alignment. - Adds a “Copy” button in the top right corner.
- Wraps each line in
<span>
tags so we can style line numbers. - Handles copying text to the clipboard and showing a “Copied” confirmation.
- Wraps each
Step 3 — Testing
- Add a Gutenberg
Code
block with some multi-line code. - Try normal width, wide width, and full width.
- Verify that line numbers align neatly and the button works on mobile and desktop.
Screenshots
Before

After

Conclusion
With this snippet, your Gutenberg code blocks are now much more developer-friendly and user-friendly.
It’s lightweight, doesn’t require extra CSS files, and works in all alignment modes.
Resources used.
ChatGPT.com for producing the code which took a few hours to get it the way I wanted and for structuring and writing most of this tutorial.
Now where is my own creative writing in this process. Well I am exploring…I do not have the same ownership of my tutorials any longer and I am not sure how I feel about that….I go through and decide what to use or not.