Adding code blocks to articles

highlight.js 11.11.1

highlight.js 11.11.1


Quick start (CDN + recommended v11 initialization)

<head></body>deferhljs.highlightAll()

<!-- Theme (light by default) --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github.min.css"> <!-- Library (full build) --> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js" defer></script> <!-- Initialization for v11 --> <script> document.addEventListener('DOMContentLoaded', function () { hljs.highlightAll(); }); </script>

Minimum markup of the code block

<pre><code class="language-php"> <?php echo "Hello, world!\n"; </code></pre>

Recommendations:

  • class="language-php"language-jslanguage-bash

  • <>


Markdown and "clean" HTML

Markdown:

```php echo "Hello, world!\n"; ```

HTML:

<pre><code class="language-html">&lt;div class="card"&gt;…&lt;/div&gt;</code></pre>

Dynamically added code (SPA/editor/AJAX)

hljs.highlightElement(el)

<script> function highlightNewCode(root=document) { root.querySelectorAll('pre code').forEach(function (block) { hljs.highlightElement(block); }); } // Пример: // const frag=document.createRange().createContextualFragment('<pre><code class="language-js">const x=42;</code></pre>'); // document.body.appendChild(frag); // highlightNewCode(frag); </script>

Themes, light/dark scheme

prefers-color-scheme

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github.min.css" media="(prefers-color-scheme: light)"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/monokai-sublime.min.css" media="(prefers-color-scheme: dark)">

githubatom-one-darkmonokai-sublimesolarized-lightvs2015


The "Copy" button (UX improvement)

<style> pre { position: relative; } .copy-btn { position: absolute; top: .5rem; right: .5rem; padding: .25rem .5rem; border: 0; border-radius: .375rem; background: rgba(0,0,0,.6); color: #fff; font: 12px/1 system-ui, sans-serif; cursor: pointer; opacity: .8; } .copy-btn:focus, .copy-btn:hover { opacity: 1; } pre code { display: block; padding: 1rem; overflow: auto; } </style> <script> document.addEventListener('DOMContentLoaded', function () { document.querySelectorAll('pre').forEach(function (pre) { if (pre.querySelector('.copy-btn')) return; const btn=document.createElement('button'); btn.className='copy-btn'; btn.type='button'; btn.setAttribute('aria-label', 'Скопировать код'); btn.textContent='Copy'; btn.addEventListener('click', async function () { const text=pre.querySelector('code')?.innerText || ''; try { await navigator.clipboard.writeText(text); btn.textContent='Copied!'; } catch { const ta=document.createElement('textarea'); ta.value=text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); btn.textContent='Copied!'; } setTimeout(()=> btn.textContent='Copy', 1200); }); pre.appendChild(btn); }); }); </script>

Line numbering (optional)

highlightjs-line-numbers.js

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/styles/line-numbers.min.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js" defer></script> <script> document.addEventListener('DOMContentLoaded', function () { hljs.highlightAll(); // подсветка if (hljs.initLineNumbersOnLoad) { hljs.initLineNumbersOnLoad(); // нумерация } }); </script> <!-- Добавьте класс line-numbers к <pre> --> <pre class="line-numbers"><code class="language-js">const x=42;</code></pre>

dynamically

hljs.highlightElement(block); if (window.hljs?.lineNumbersBlock) hljs.lineNumbersBlock(block);

Efficiency

  • Only the necessary languages.

    <!-- Минимальное ядро --> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/core.min.js" defer></script> <!-- Нужные языки --> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/php.min.js" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js" defer></script> <script> document.addEventListener('DOMContentLoaded', ()=> hljs.highlightAll()); </script>
  • Disable auto-detection.language-xxx

  • Delayed loading.defer

  • Long fragments.overflow: auto;


Accessibility (a11y) and amenities

  • <figure>

    <figure> <figcaption>Example of an API request (PHP)</figcaption> <pre><code class="language-php">...</code></pre> </figure>
  • Give the "Copy" button visible focus.

  • tab-size: 2;

    pre code { tab-size: 2; }

Security when inserting the code

If the code comes from users (comments, CMS plugins):

  • Sanitize the input HTML on the server/client.

  • textContentinnerHTML

    const code=document.createElement('code'); code.className='language-js'; code.textContent='alert("hi")'; // safe

Common mistakes when switching to v11

  1. The old initialization.hljs.initHighlightingOnLoad()hljs.highlightBlock(...)hljs.highlightAll()hljs.highlightElement(...)

  2. There is no explicit language.

  3. Unshielded HTML code.

  4. Reconnecting themes/scripts.

  5. We connected the "full" bundle on each page.


Alternatives

  • Prism.js

  • Shiki


hljs 11.11.1

<!doctype html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Демо подсветки кода (hljs 11.11.1)</title> <!-- Светлая/тёмная темы --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github.min.css" media="(prefers-color-scheme: light)"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/monokai-sublime.min.css" media="(prefers-color-scheme: dark)"> <style> body { margin: 2rem; font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; } pre { position: relative; margin: 1rem 0; border-radius: .5rem; } pre code { display: block; padding: 1rem; overflow: auto; } .copy-btn { position: absolute; top: .5rem; right: .5rem; padding: .25rem .5rem; border: 0; border-radius: .375rem; background: rgba(0,0,0,.6); color: #fff; font-size: 12px; cursor: pointer; opacity: .85; } .copy-btn:focus, .copy-btn:hover { opacity: 1; outline: 2px solid rgba(0,0,0,.2); } </style> <!-- Библиотека: ядро + нужные языки --> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/core.min.js" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/php.min.js" defer></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js" defer></script> <script> document.addEventListener('DOMContentLoaded', function () { hljs.highlightAll(); // Кнопки «Copy» document.querySelectorAll('pre').forEach(function (pre) { const btn=document.createElement('button'); btn.className='copy-btn'; btn.type='button'; btn.textContent='Copy'; btn.setAttribute('aria-label', 'Скопировать код'); btn.addEventListener('click', async function () { const text=pre.querySelector('code')?.innerText || ''; try { await navigator.clipboard.writeText(text); btn.textContent='Copied!'; } catch {} setTimeout(()=> btn.textContent='Copy', 1200); }); pre.appendChild(btn); }); }); </script> </head> <body> <h1>Пример подсветки</h1> <figure> <figcaption>PHP: базовый вывод</figcaption> <pre><code class="language-php"><?php echo "Hello, world!\n"; ?></code></pre> </figure> <figure> <figcaption>JavaScript: функция суммы</figcaption> <pre><code class="language-javascript">const sum=(a, b)=> a + b;</code></pre> </figure> </body> </html>

didn't run in the browser?

show code as text

Static markup (Markdown/HTML)

  • Markdown-fences

    ```html <script>alert(1)</script> ```
  • screen it&lt;&gt;&amp;

    <pre><code class="language-html">&lt;script&gt;alert(1)&lt;/script&gt;</code></pre>

Dynamic insertion (SPA, AJAX, editors)

innerHTMLtextContentcreateTextNode

<script> function insertCode(lang, rawText, root=document.body) { const pre=document.createElement('pre'); const code=document.createElement('code'); code.className='language-' + lang; // example: language-html code.textContent=rawText; // ← safe: tags are not parsed pre.appendChild(code); root.appendChild(pre); hljs.highlightElement(code); // hljs 11.x } // Пример: // insertCode('html', '<script>alert(1)</script>'); </script>

Sanitizing user input

sanitize<script>on*javascript:<style><iframe>

textContent

CSP is a protective "grid" at the header level

Content-Security-Policy

# Пример для Nginx (адаптируйте под свой стек) add_header Content-Security-Policy " default-src 'self'; script-src 'self' https://cdnjs.cloudflare.com; style-src 'self' https://cdnjs.cloudflare.com 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; " always;

https://cdnjs.cloudflare.com

script-src 'unsafe-inline'

Templating tools and engines

  • Blade (Laravel):@if{{ }}@verbatim … @endverbatim

  • Twig/You need to literally output the tags:verbatim

  • Vue/React in the text:{{ mustache }}

Sandbox (for previewing someone else's HTML)

ready-made HTML<iframe sandbox>withoutallow-scripts

<iframe sandbox srcdoc="<h1>This is just a preview</h1&gt;&lt;script&gt;alert(1)&lt;/script&gt;"> </iframe>

Scripts inside such an iframe will not be executed. To highlight the "code" it is better to stick to 13.1–13.2.

Total

textContentinnerHTML<script>…</script>