Prism.js Code Block with Copy Button — Free JS Snippet

Prism.js Code Block with Copy Button and Line Highlight · Layouts · Plain HTML, CSS & JS · Live preview

CategoryLayouts

What's included

Features

Syntax highlighting with the Tomorrow theme
Line-numbers plug-in enabled by a class on the pre element
Clickable line numbers that toggle highlighted lines
Highlight bars rebuilt through the line-highlight plug-in API
Copy button that copies textContent so no markup leaks in
Async clipboard with an execCommand fallback and honest failure message
Editor-style header with file name and traffic-light dots
Event delegation on the pre element for dynamically generated line numbers

About this UI Snippet

Prism.js Code Block with Copy and Line Highlight — HTML, CSS & JavaScript

Screenshot of the Prism.js Code Block with Copy Button and Line Highlight snippet rendered live

Code blocks are the most-read component on a developer blog and the one most often left half-finished. Highlighting is the easy part; readers also want line numbers to refer to, a way to point at the lines that matter, and a copy button that works. Prism.js is a lightweight syntax highlighter built for exactly this kind of documentation: it tokenises code into spans you can style with a theme, and its plug-in system adds line numbers and line highlighting without touching the core.

Prism's plug-ins are opt-in and mostly declarative. Adding the class line-numbers to the pre element turns on the number gutter, and a data-line attribute such as "4-6" or "1,3,8-10" turns on highlighted bars. Both plug-ins need their own stylesheet as well as their script, and highlighting runs when Prism.highlightElement(code) is called — here explicitly, because the demo loads scripts dynamically after the page is built, so Prism's automatic run on window load may already have passed.

Interactive highlighting is where the snippet goes beyond the documentation. Prism draws its line-highlight bars as absolutely positioned elements measured once when the plug-in runs, so changing data-line later does nothing on its own. The paintLines() function updates the attribute, removes the existing .line-highlight nodes and re-invokes Prism.plugins.lineHighlight.highlightLines(pre)(), which rebuilds them. Clicking a line number toggles that line in a Set. The gutter is generated after highlighting, so the click handler is delegated to the pre element and finds the row number from the child's index — a delegation pattern that avoids attaching handlers to elements that do not exist yet.

The copy button has its own subtlety: it copies code.textContent, not innerHTML. After highlighting, the code element is full of token spans, but textContent returns only the original characters, so the clipboard gets clean source. The async navigator.clipboard.writeText needs a secure context and permission and can reject inside embedded frames, so the handler falls back to a hidden textarea and document.execCommand('copy'), and if both fail the button asks the user to press Ctrl+C rather than pretending success. The header mimics an editor window chrome and shows the file name, which gives code samples context at a glance.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant like Claude to add language tabs, a diff mode that shades added and removed lines, or a collapse toggle for long listings.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a code block with Prism.js 1.29 loaded from cdnjs (core script, the Tomorrow theme, and the line-numbers and line-highlight plug-ins with their CSS).

Requirements:
- Render a <pre class="line-numbers" data-line="4-6"><code class="language-javascript"> with a header showing a file name and a Copy button.
- Call Prism.highlightElement(code) explicitly.
- Make line numbers clickable: keep the highlighted lines in a Set, update data-line, remove existing .line-highlight nodes and call Prism.plugins.lineHighlight.highlightLines(pre)() to redraw; delegate the click to the pre element.
- Implement Copy with navigator.clipboard.writeText(code.textContent) and a hidden-textarea execCommand fallback, showing a "Copied!" state.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<figure class="pc-block">
  <header class="pc-head">
    <span class="pc-file"><i></i><i></i><i></i> useDebounce.js</span>
    <span class="pc-hint" id="pcHint">Click a line number to highlight it</span>
    <button type="button" class="pc-copy" id="pcCopy" aria-live="polite">Copy</button>
  </header>
  <pre class="line-numbers" id="pcPre" data-line="4-6"><code class="language-javascript" id="pcCode">import { useEffect, useState } from 'react';

export function useDebounce(value, delay = 300) {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id); // cancel on change or unmount
  }, [value, delay]);

  return debounced;
}</code></pre>
</figure>

Step by step

How to Use

  1. 1
    Read the highlighted codeLines 4 to 6 are highlighted by default with a soft bar to draw attention to the cleanup logic.
  2. 2
    Toggle a lineClick any line number to add or remove its highlight. The hint text lists the highlighted lines.
  3. 3
    Copy the codePress Copy. The button confirms with "Copied!" and the clipboard contains clean source, without markup.
  4. 4
    Check the fallbackIf the async clipboard is blocked, the button falls back to a hidden-textarea copy automatically.
  5. 5
    Reuse itChange the language class and code to reuse the block for CSS, HTML or any Prism-supported language.

Real-world uses

Common Use Cases

Developer blogs and tutorials
Give every code sample line numbers and a copy button. For an editable version see the CodeMirror editor.
API and product documentation
Highlight the exact lines a paragraph is talking about.
Course material
Point students at specific lines in a longer listing by number.
ADMIN
Changelog and diff-style displays
Emphasise changed lines in configuration and release-note snippets.

Got questions?

Frequently Asked Questions

Prism plug-ins split behaviour and styling. The script adds the feature, and the CSS supplies the gutter and highlight bar appearance.

The bars are drawn once. Remove the old .line-highlight elements and call Prism.plugins.lineHighlight.highlightLines(pre)() again after changing the attribute.

After highlighting, the code element contains many token spans. textContent returns only the original text, so the clipboard is clean.

It requires a secure context and permission. Fall back to a hidden textarea with document.execCommand("copy"), and tell the user if that fails too.

Load that language's component script, then use class="language-python" (or similar) on the code element and call Prism.highlightElement.

Set data-line="2-5" on the pre element. Ranges and comma-separated numbers can be combined, such as "1,3,8-10".

Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from Prism.js, so in a framework project install it with npm install prismjs instead of the CDN tag, call Prism.highlightElement in useEffect / onMounted / ngAfterViewInit after the code renders, and release it with nothing (it does not attach listeners) when the component unmounts.