WordPress Code-Blöcke aufwerten: Zeilennummern und Copy-Button ohne Plugin
Wer technische Inhalte auf WordPress veröffentlicht, kennt das Problem. Die Standard-Codeblöcke des Gutenberg-Editors sehen schlicht aus, bieten keine Zeilennummern und haben keinen Button zum Kopieren. Besucher müssen den Code manuell markieren und dann erst kopieren. Das ist unpraktisch und wirkt wenig professionell.
Die gute Nachricht: Es braucht kein Plugin dafür. Mit etwas PHP und JavaScript lässt sich das direkt im Theme lösen. Der folgende Ansatz erweitert bestehende Gutenberg-Codeblöcke automatisch um ein dunkles Monokai-Design, Zeilennummern und einen COPY-Button.
Wie funktioniert das Ganze?
Der Code hängt sich über einen WordPress-Hook (wp_enqueue_scripts) in den Seitenaufbau ein. Er läuft nur auf einzelnen Blogbeiträgen, also nicht auf Seiten, Archiven oder dem Startseite. Das hält die Performance sauber.
Beim Laden der Seite sucht ein kleines JavaScript nach allen vorhandenen wp-block-code-Elementen. Jeden gefundenen Codeblock verpackt es in ein Wrapper-Div, fügt einen Copy-Button ein und teilt den Codeinhalt zeilenweise in einzelne span-Elemente auf. Die Zeilennummern kommen dann per CSS-Counter, also ohne zusätzliche HTML-Elemente.
Das Design orientiert sich an Monokai Pro, einem der beliebtesten Dark-Themes unter Entwicklern. Dunkler Hintergrund (#272822), heller Code-Text (#f8f8f2) und grüne Akzente (#a6e22e) machen die Blöcke optisch sofort erkennbar.
Der Copy-Button im Detail
Beim Klick auf COPY wird der Inhalt des Codeblocks in die Zwischenablage geschrieben. Dabei gibt es einen wichtigen Punkt, den viele ähnliche Lösungen falsch machen. Die Zeilennummern werden über CSS-Pseudoelemente (::before) dargestellt. In Chrome liest innerText diese Pseudoinhalte mit aus, was bedeutet, der kopierte Text enthält plötzlich "1 2 3 …" am Anfang jeder Zeile.
Die Lösung: Statt innerText direkt auf dem code-Element aufzurufen, werden alle span-Kindelemente einzeln ausgelesen und dann zusammengefügt. So landet nur der echte Code in der Zwischenablage, ohne Zeilennummern.
Nach dem Klick zeigt der Button kurz COPIED! in Grün an und springt nach zwei Sekunden zurück zum ursprünglichen Zustand.
Einbindung ins Theme
Der Code kommt in die functions.php des (Child-)Themes oder via Snippet Plugin. Wer MainWP oder ein Deployment-Setup nutzt, kann ihn auch als eigenständiges Mini-Plugin einbinden. Dafür reicht eine einfache PHP-Datei mit dem Plugin-Header im wp-content/plugins-Verzeichnis.
Ein Hinweis zur Einbindung: Das Inline-CSS wird über ein eigenes Style-Handle registriert (code-enhancer-style). Der ursprüngliche Ansatz, das CSS an wp-block-library anzuhängen, funktioniert meistens, ist aber nicht zuverlässig. wp-block-library wird von WordPress nur eingebunden, wenn tatsächlich Gutenberg-Blöcke auf der Seite vorhanden sind. Ein eigenes Handle ist stabiler.
Ähnlich beim JavaScript: wp_register_script mit false als Source-Angabe ist der saubere Weg, um Inline-Scripts anzuhängen, ohne eine externe Datei zu laden.
Für wen lohnt sich das?
Für alle, die regelmäßig Code auf ihrer WordPress-Seite veröffentlichen. Tutorials, Snippets, Plugin-Dokumentationen, technische Anleitungen. Überall dort, wo Leser den Code auch wirklich nutzen wollen, verbessert ein Copy-Button die Nutzererfahrung spürbar.
Fortgeschrittene können das Snippet als Ausgangsbasis nehmen und es weiter anpassen: andere Farben, Syntax-Highlighting über Prism.js, oder eine Anzeige der verwendeten Programmiersprache im Codeblock-Header.
Der vollständige Code findest du hier, bereit zum Kopieren.
Code:
<?php
add_action('wp_enqueue_scripts', function () {
// Führe den Code nur aus, wenn ein einzelner Blogbeitrag angezeigt wird
if (!is_singular('post')) {
return;
}
/* CSS: Monokai Theme Style */
$css = <<<CSS
.code-copy-wrapper {
position: relative;
margin: 1.5em 0;
background: #272822;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0,0,0,0.25);
border: 1px solid #1e1f1c;
}
.code-copy-wrapper::before {
content: "";
display: block;
height: 10px;
background: #1e1f1c;
width: 100%;
}
.code-copy-wrapper pre.wp-block-code {
position: relative;
background: #272822 !important;
border: none !important;
padding: 1.2em 0;
margin: 0;
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: #49483e #272822;
}
.code-copy-wrapper pre.wp-block-code code {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
font-size: 14px;
line-height: 1.65;
color: #f8f8f2;
display: block;
counter-reset: linenumber;
}
.code-copy-wrapper pre.wp-block-code code > span {
display: block;
counter-increment: linenumber;
padding-left: 4em;
position: relative;
}
.code-copy-wrapper pre.wp-block-code code > span:hover {
background: #3e3d32;
}
.code-copy-wrapper pre.wp-block-code code > span::before {
content: counter(linenumber);
position: absolute;
left: 0;
width: 3.2em;
text-align: right;
padding-right: 1em;
color: #90908a;
border-right: 1px solid #49483e;
}
.code-copy-btn {
position: absolute;
top: 8px;
right: 10px;
background: #49483e;
border: none;
border-radius: 3px;
cursor: pointer;
opacity: 0.6;
transition: all 0.2s ease;
z-index: 10;
padding: 4px 10px;
font-size: 11px;
color: #f8f8f2;
display: flex;
align-items: center;
gap: 6px;
font-weight: bold;
}
.code-copy-wrapper:hover .code-copy-btn {
opacity: 1;
}
.code-copy-btn:hover {
background: #a6e22e;
color: #272822;
}
CSS;
// Fix #1: Saubere Registrierung ohne leeren Source-String
wp_register_script('code-enhancer', false, [], false, ['in_footer' => true]);
wp_enqueue_script('code-enhancer');
// Fix #2: Eigenes Style-Handle statt wp-block-library (nicht immer vorhanden)
wp_register_style('code-enhancer-style', false);
wp_enqueue_style('code-enhancer-style');
wp_add_inline_style('code-enhancer-style', $css);
/* JS: Logik für Button und Zeilenumbruch */
$js = <<<JS
document.addEventListener('DOMContentLoaded', function () {
const icon = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><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>';
document.querySelectorAll('pre.wp-block-code').forEach(pre => {
if (pre.querySelector('.code-copy-btn')) return;
const wrapper = document.createElement('div');
wrapper.className = 'code-copy-wrapper';
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const button = document.createElement('button');
button.className = 'code-copy-btn';
button.innerHTML = icon + ' COPY';
wrapper.appendChild(button);
const code = pre.querySelector('code');
if (code && !code.dataset.linesWrapped) {
const lines = code.innerText.trim().split('\\n');
code.innerHTML = lines.map(line =>
'<span>' + (line === '' ? '<br>' : line.replace(/[&<>"']/g, m => ({'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}[m]))) + '</span>'
).join('');
code.dataset.linesWrapped = 'true';
}
button.addEventListener('click', () => {
// Fix #3: Zeilennummern (::before CSS-Pseudoelemente) aus dem Clipboard-Text ausschließen
const lines = Array.from(code.querySelectorAll('span'))
.map(s => s.innerText.replace(/\\n$/, ''))
.join('\\n');
navigator.clipboard.writeText(lines).then(() => {
const originalHTML = button.innerHTML;
button.textContent = 'COPIED!';
button.style.background = '#a6e22e';
button.style.color = '#272822';
setTimeout(() => {
button.innerHTML = originalHTML;
button.style.background = '';
button.style.color = '';
}, 2000);
});
});
});
});
JS;
wp_add_inline_script('code-enhancer', $js);
});