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>