highlight.js vs prismjs vs react-syntax-highlighter vs shiki
Syntax Highlighting Engines: Architecture, Performance, and Framework Integration
highlight.jsprismjsreact-syntax-highlightershikiSimilar Packages:

Syntax Highlighting Engines: Architecture, Performance, and Framework Integration

highlight.js, prismjs, react-syntax-highlighter, and shiki are the leading solutions for adding syntax highlighting to code blocks in web applications. highlight.js is a mature, auto-detecting engine that runs primarily in the browser. prismjs is a lightweight, modular library focused on extensibility via plugins. react-syntax-highlighter is a React-specific wrapper that simplifies using highlight.js or prismjs within component trees. shiki is a modern, Node-based engine that uses TextMate grammars to produce VS Code-accurate highlighting, typically requiring build-time processing. Choosing the right tool depends on whether you prioritize zero-config auto-detection, plugin ecosystems, React integration ease, or pixel-perfect theme accuracy.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
highlight.js025,0085.5 MB1292 months agoBSD-3-Clause
prismjs013,0522.05 MB5182 years agoMIT
react-syntax-highlighter04,6732.19 MB13816 days agoMIT
shiki013,881602 kB12024 days agoMIT

Syntax Highlighting Engines: Architecture, Performance, and Framework Integration

When displaying code on the web, developers often reach for the first library they find. However, the four major players—highlight.js, prismjs, react-syntax-highlighter, and shiki—solve the problem in fundamentally different ways. Understanding these architectural differences is critical because they dictate your application's performance, bundle size, and visual accuracy.

Let's break down how these libraries handle the core challenges of syntax highlighting.

🧠 Language Detection: Auto-Magic vs. Explicit Declaration

How the library identifies the code language changes how you write your HTML and how fast your page loads.

highlight.js is famous for its auto-detection. It scans the code content and guesses the language. This is great for user-generated content but can be slow and occasionally incorrect.

// highlight.js: Auto-detects language based on content
import hljs from 'highlight.js';

// No need to specify 'javascript' explicitly
hljs.highlightElement(document.querySelector('pre code'));

prismjs requires you to explicitly declare the language in your HTML class names. It does not guess. This makes it faster and more predictable but requires stricter markup.

// prismjs: Requires explicit language class
import Prism from 'prismjs';
import 'prismjs/components/prism-javascript';

// You must add class="language-javascript" to the HTML
Prism.highlightAll();

react-syntax-highlighter passes the language explicitly as a prop to the React component. It relies on the underlying engine (HLJS or Prism) but abstracts the DOM manipulation away.

// react-syntax-highlighter: Explicit language prop
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
import js from 'react-syntax-highlighter/dist/esm/languages/hljs/javascript';

SyntaxHighlighter.registerLanguage('javascript', js);

// Usage in JSX
<SyntaxHighlighter language="javascript">
  {codeString}
</SyntaxHighlighter>

shiki also requires explicit language specification. Because it uses complex TextMate grammars, auto-detection would be too heavy for client-side use. It is typically used with the language defined in the function call.

// shiki: Explicit language in the highlighter call
import { getHighlighter } from 'shiki';

const highlighter = await getHighlighter({ theme: 'nord' });

// Must specify language: 'js'
const html = highlighter.codeToHtml('console.log("hi")', { lang: 'js' });

🎨 Theme Accuracy: Approximation vs. VS Code Parity

The visual quality of your code blocks depends on how the library generates colors. Some approximate themes, while others use the exact same engine as VS Code.

highlight.js uses its own set of CSS-based themes. They look good but are approximations. You cannot easily import a .tmTheme file from VS Code; you must find or write a CSS file that matches the colors.

/* highlight.js: Uses CSS classes for colors */
.hljs-keyword { color: #a626a4; }
.hljs-string { color: #50a14f; }
/* You are limited to the CSS themes provided by the community */

prismjs similarly relies on CSS themes. It offers a wide variety of community themes, but like HLJS, it does not natively support VS Code theme files. You are stuck with the available CSS stylesheets.

/* prismjs: CSS-based theming */
.token.keyword { color: #f92672; }
.token.string { color: #e6db74; }
/* Theming is done via standard CSS overrides */

react-syntax-highlighter inherits the theming limitations of its underlying engine. If you use the HLJS version, you get HLJS themes. If you use the Prism version, you get Prism themes. It does not add new theming capabilities itself.

// react-syntax-highlighter: Imports pre-made CSS themes
import { vs } from 'react-syntax-highlighter/dist/esm/styles/hljs';

// The style object generates the CSS for you
<SyntaxHighlighter style={vs}>
  {codeString}
</SyntaxHighlighter>

shiki is the outlier here. It uses actual TextMate grammars and VS Code theme JSON files. The output is pixel-perfect compared to your editor. If you love the "One Dark Pro" theme in VS Code, you can use the exact same colors on the web.

// shiki: Uses real VS Code themes
import { getHighlighter } from 'shiki';

// Loads the actual 'dracula' theme JSON
const highlighter = await getHighlighter({ theme: 'dracula' });

// Output HTML has inline styles matching VS Code exactly
const html = highlighter.codeToHtml('const x = 1;', { lang: 'js' });

⚡ Performance & Runtime: Browser vs. Build-Time

Where the highlighting happens determines your user's experience. Running complex regex in the browser can cause lag, while doing it at build time makes pages instant.

highlight.js runs entirely in the browser. When a page loads, it scans the DOM, parses every code block, and applies classes. On a page with 50 code blocks, this can cause visible layout shifts and jank.

// highlight.js: Runs on client-side load
// This blocks the main thread while parsing
hljs.highlightAll(); 

prismjs also runs in the browser. While it is generally faster than highlight.js due to its modular design, it still consumes client-side CPU to parse code on every page visit.

// prismjs: Client-side execution
// Parses code when the DOM is ready
Prism.highlightAll();

react-syntax-highlighter runs in the browser during the React render cycle. For large code blocks, this can cause slow component mounting and poor interaction readiness (Time to Interactive).

// react-syntax-highlighter: Client-side rendering
// The highlighting logic runs inside the useEffect or render
return <SyntaxHighlighter>{largeCodeBlock}</SyntaxHighlighter>;

shiki is designed to run at build time (in Node.js) or server-side. It generates static HTML with inline styles. The browser receives ready-to-display HTML, resulting in zero client-side processing for highlighting.

// shiki: Build-time generation (e.g., in Next.js getStaticProps)
// The heavy lifting happens on the server
export async function getStaticProps() {
  const html = await highlighter.codeToHtml(code, { lang: 'ts' });
  return { props: { codeHtml: html } };
}

🔌 Extensibility: Plugins vs. Grammar Complexity

Sometimes you need more than just colors. You might need line numbers, copy buttons, or diff highlighting.

highlight.js has a plugin system, but it is less active than Prism's. Adding features like line numbers often requires third-party scripts that hook into the HLJS DOM output, which can be fragile.

// highlight.js: Limited plugin ecosystem
// Often requires manual DOM manipulation after highlighting
hljs.initLineNumbersOnLoad(); // Example of a common addon

prismjs shines here. It has a massive ecosystem of official plugins for line numbers, copy-to-clipboard, command-line prompts, and more. You simply import the plugin JS and CSS.

// prismjs: Rich plugin system
import 'prismjs/plugins/line-numbers/prism-line-numbers.js';
import 'prismjs/plugins/line-numbers/prism-line-numbers.css';

// Automatically adds line numbers to pre tags with the class

react-syntax-highlighter makes using these plugins easier in React. It often wraps the plugin functionality into props, so you don't have to manage useEffect hooks to initialize Prism plugins manually.

// react-syntax-highlighter: Abstracts plugin usage
// Some versions allow passing plugin configs directly as props
<SyntaxHighlighter showLineNumbers={true}>
  {codeString}
</SyntaxHighlighter>

shiki does not have a plugin system in the traditional sense. Because it outputs raw HTML with inline styles, features like line numbers must be implemented by wrapping the output in your own CSS or HTML structure. It prioritizes highlighting accuracy over extra features.

// shiki: No built-in line number plugins
// You must wrap the result and style it yourself
const html = highlighter.codeToHtml(code, { lang: 'js' });
// Developer adds CSS counters or grid layout for line numbers manually

🌱 Similarities: Shared Ground

Despite their differences, these libraries share common goals and some overlapping capabilities.

1. 📝 Support for Common Languages

All four libraries support the major languages (JavaScript, Python, CSS, HTML, Rust, Go). For standard web development tasks, any of them will provide adequate highlighting for mainstream syntax.

// All support basic JS highlighting
// hljs.highlight(code, { language: 'javascript' })
// Prism.highlight(code, Prism.languages.javascript, 'javascript')
// <SyntaxHighlighter language="javascript" />
// highlighter.codeToHtml(code, { lang: 'js' })

2. 🌍 Community Themes

Each library has a vibrant community creating themes. While shiki uses VS Code themes directly, the others have ports of popular themes like Dracula, Solarized, and GitHub Dark available via npm.

// Importing a dark theme is common across all
// hljs: import 'highlight.js/styles/github-dark.css'
// prism: import 'prismjs/themes/prism-tomorrow.css'
// shiki: theme: 'github-dark'

3. 🛠️ Framework Integration

While react-syntax-highlighter is React-specific, wrappers exist for Vue, Svelte, and Angular for both highlight.js and prismjs. shiki is framework-agnostic regarding output (HTML strings), making it easy to integrate into any framework that supports server-side rendering.

// Conceptual integration pattern for all
// 1. Import library
// 2. Load language definition
// 3. Pass code string
// 4. Render result (DOM or HTML string)

📊 Summary: Key Differences

Featurehighlight.jsprismjsreact-syntax-highlightershiki
Detection✅ Auto-detect❌ Explicit only❌ Explicit prop❌ Explicit only
Theme EngineCSS ClassesCSS ClassesCSS ClassesVS Code JSON
ExecutionBrowser (Runtime)Browser (Runtime)Browser (Runtime)Node/Build-time
PluginsLimitedExtensiveWrapped PluginsNone (DIY)
Best ForBlogs, Auto-langDocs, FeaturesReact AppsDesign Systems

💡 The Big Picture

highlight.js is the "set it and forget it" option. Use it when you don't control the input language or want minimal setup. It is reliable but showing its age in terms of performance and theme flexibility.

prismjs is the developer's choice for documentation. If you need line numbers, copy buttons, or specific language tweaks, its plugin ecosystem is unmatched. It strikes a great balance between size and features.

react-syntax-highlighter is the pragmatic choice for React teams. It saves hours of boilerplate code wrapping Prism or HLJS. Use it to ship features fast, but keep an eye on performance if you render hundreds of blocks.

shiki is the premium choice for visual fidelity. If your brand relies on perfect design matching and you use static site generation, shiki provides the best-looking code blocks possible. The trade-off is complexity: you must move highlighting to the server or build step.

Final Thought: There is no single "best" library. If you are building a quick blog, highlight.js is fine. If you are building a design system for a dev tool, shiki is worth the extra effort. Choose based on where you want to pay the cost: in bundle size, runtime performance, or build complexity.

How to Choose: highlight.js vs prismjs vs react-syntax-highlighter vs shiki

  • highlight.js:

    Choose highlight.js if you need a robust, zero-configuration solution that automatically detects languages without explicit markup. It is ideal for blogs, documentation sites, or user-generated content where the language might be unknown. However, be aware that it runs in the browser, which can impact performance on pages with many code blocks, and its themes are less customizable than modern alternatives.

  • prismjs:

    Choose prismjs if you need a lightweight core with a rich ecosystem of plugins for features like line numbers, copy buttons, or command-line prompts. It is perfect for technical documentation where you need fine-grained control over the highlighting process and specific language definitions. Its modular architecture allows you to tree-shake unused languages, keeping bundle sizes small.

  • react-syntax-highlighter:

    Choose react-syntax-highlighter if you are building a React application and want a drop-in component that handles the complexity of integrating highlight.js or prismjs. It is the best choice for rapid development when you need immediate React compatibility without writing custom wrappers or managing DOM refs manually. Note that it inherits the runtime performance characteristics of the underlying engine it wraps.

  • shiki:

    Choose shiki if visual fidelity to VS Code themes is your top priority and you can perform highlighting at build time or server-side. It is the superior choice for design systems, high-end documentation, or static sites where you want exact color matching and don't want to ship heavy grammar files to the client. Avoid it for purely client-side dynamic content unless you implement complex web worker strategies.

README for highlight.js

Highlight.js

latest version license install size minified NPM downloads weekly jsDelivr CDN downloads

ci status CodeQL vulnerabilities

discord open issues help welcome issues good first issue

Highlight.js is a syntax highlighter written in JavaScript. It works in the browser as well as on the server. It can work with pretty much any markup, doesn’t depend on any other frameworks, and has automatic language detection.

Contents


Upgrading to Version 11

As always, major releases do contain breaking changes which may require action from users. Please read VERSION_11_UPGRADE.md for a detailed summary of breaking changes and any actions you may need to take.

Support for older versions

Please see SECURITY.md for long-term support information.


Basic Usage

In the Browser

The bare minimum for using highlight.js on a web page is linking to the library along with one of the themes and calling highlightAll:

<link rel="stylesheet" href="/path/to/styles/default.min.css">
<script src="/path/to/highlight.min.js"></script>
<script>hljs.highlightAll();</script>

This will find and highlight code inside of <pre><code> tags; it tries to detect the language automatically. If automatic detection doesn’t work for you, or you simply prefer to be explicit, you can specify the language manually by using the class attribute:

<pre><code class="language-html">...</code></pre>

Plaintext Code Blocks

To apply the Highlight.js styling to plaintext without actually highlighting it, use the plaintext language:

<pre><code class="language-plaintext">...</code></pre>

Ignoring a Code Block

To skip highlighting of a code block completely, use the nohighlight class:

<pre><code class="nohighlight">...</code></pre>

Node.js on the Server

The bare minimum to auto-detect the language and highlight some code.

// load the library and ALL languages
hljs = require('highlight.js');
html = hljs.highlightAuto('<h1>Hello World!</h1>').value

To load only a "common" subset of popular languages:

hljs = require('highlight.js/lib/common');

To highlight code with a specific language, use highlight:

html = hljs.highlight('<h1>Hello World!</h1>', {language: 'xml'}).value

See Importing the Library for more examples of require vs import usage, etc. For more information about the result object returned by highlight or highlightAuto refer to the api docs.

Supported Languages

Highlight.js supports over 180 languages in the core library. There are also 3rd party language definitions available to support even more languages. You can find the full list of supported languages in SUPPORTED_LANGUAGES.md.

Custom Usage

If you need a bit more control over the initialization of Highlight.js, you can use the highlightElement and configure functions. This allows you to better control what to highlight and when.

For example, here’s the rough equivalent of calling highlightAll but doing the work manually instead:

document.addEventListener('DOMContentLoaded', (event) => {
  document.querySelectorAll('pre code').forEach((el) => {
    hljs.highlightElement(el);
  });
});

Please refer to the documentation for configure options.

Using custom HTML

We strongly recommend <pre><code> wrapping for code blocks. It's quite semantic and "just works" out of the box with zero fiddling. It is possible to use other HTML elements (or combos), but you may need to pay special attention to preserving linebreaks.

Let's say your markup for code blocks uses divs:

<div class='code'>...</div>

To highlight such blocks manually:

// first, find all the div.code blocks
document.querySelectorAll('div.code').forEach(el => {
  // then highlight each
  hljs.highlightElement(el);
});

Without using a tag that preserves linebreaks (like pre) you'll need some additional CSS to help preserve them. You could also pre and post-process line breaks with a plug-in, but we recommend using CSS.

To preserve linebreaks inside a div using CSS:

div.code {
  white-space: pre;
}

Using with Vue.js

See highlightjs/vue-plugin for a simple Vue plugin that works great with Highlight.js.

An example of vue-plugin in action:

  <div id="app">
    <!-- bind to a data property named `code` -->
    <highlightjs autodetect :code="code" />
    <!-- or literal code works as well -->
    <highlightjs language='javascript' code="var x = 5;" />
  </div>

Using Web Workers

You can run highlighting inside a web worker to avoid freezing the browser window while dealing with very big chunks of code.

In your main script:

addEventListener('load', () => {
  const code = document.querySelector('#code');
  const worker = new Worker('worker.js');
  worker.onmessage = (event) => { code.innerHTML = event.data; }
  worker.postMessage(code.textContent);
});

In worker.js:

onmessage = (event) => {
  importScripts('<path>/highlight.min.js');
  const result = self.hljs.highlightAuto(event.data);
  postMessage(result.value);
};

Importing the Library

First, you'll likely be installing the library via npm or yarn -- see Getting the Library.

Node.js CommonJS Modules / require

Requiring the top-level library will load all languages:

// require the highlight.js library, including all languages
const hljs = require('./highlight.js');
const highlightedCode = hljs.highlightAuto('<span>Hello World!</span>').value

For a smaller footprint, load our common subset of languages (the same set used for our default web build).

const hljs = require('highlight.js/lib/common');

For the smallest footprint, load only the languages you need:

const hljs = require('highlight.js/lib/core');
hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml'));

const highlightedCode = hljs.highlight('<span>Hello World!</span>', {language: 'xml'}).value

Node.js ES6 Modules / import

The default import will register all languages:

import hljs from 'highlight.js';

It is more efficient to import only the library and register the languages you need:

import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
hljs.registerLanguage('javascript', javascript);

If your build tool processes CSS imports, you can also import the theme directly as a module:

import hljs from 'highlight.js';
import 'highlight.js/styles/github.css';

Browser ES6 Modules

Note: For now you'll want to install @highlightjs/cdn-assets package instead of highlight.js. See Download prebuilt CDN assets

To import the library and register only those languages that you need:

import hljs from './assets/js/@highlightjs/cdn-assets/es/core.js';
import javascript from './assets/js/@highlightjs/cdn-assets/es/languages/javascript.min.js';

hljs.registerLanguage('javascript', javascript);

To import the library and register all languages:

import hljs from './assets/js/@highlightjs/cdn-assets/es/highlight.js';

Note: The path to these files will vary depending on where you have installed/copied them within your project or site. The above path is only an example.

You can also use importmap to import in similar way as Node:

<script type="importmap">
{
	"imports": {
		"@highlightjs": "./assets/js/@highlightjs/cdn-assets/es/"
	}
}
</script>

Use the above code in your HTML. After that, your JavaScript can import using the named key from your importmap, for example @highlightjs in this case:

import hljs from '@highlightjs/core.js';
import javascript from '@highlightjs/languages/javascript.min.js';

hljs.registerLanguage('javascript', javascript);

Note: You can also import directly from fully static URLs, such as our very own pre-built ES6 Module CDN resources. See Fetch via CDN for specific examples.

Getting the Library

You can get highlight.js as a hosted, or custom-build, browser script or as a server module. Right out of the box the browser script supports both AMD and CommonJS, so if you wish you can use RequireJS or Browserify without having to build from source. The server module also works perfectly fine with Browserify, but there is the option to use a build specific to browsers rather than something meant for a server.

Do not link to GitHub directly. The library is not supposed to work straight from the source, it requires building. If none of the pre-packaged options work for you refer to the building documentation.

On Almond. You need to use the optimizer to give the module a name. For example:

r.js -o name=hljs paths.hljs=/path/to/highlight out=highlight.js

Fetch via CDN

A prebuilt version of Highlight.js bundled with many common languages is hosted by several popular CDNs. When using Highlight.js via CDN you can use Subresource Integrity for additional security. For details see DIGESTS.md.

cdnjs (link)

Common JS
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0/highlight.min.js"></script>
<!-- and it's easy to individually load additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0/languages/go.min.js"></script>
ES6 Modules
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0/styles/dark.min.css">
<script type="module">
import hljs from 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0/es/highlight.min.js';
//  and it's easy to individually load additional languages
import go from 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0/es/languages/go.min.js';
hljs.registerLanguage('go', go);
</script>

jsdelivr (link)

Common JS
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.12.0/build/styles/default.min.css">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.12.0/build/highlight.min.js"></script>
<!-- and it's easy to individually load additional languages -->
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.12.0/build/languages/go.min.js"></script>
ES6 Modules
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.12.0/build/styles/default.min.css">
<script type="module">
import hljs from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.12.0/build/es/highlight.min.js';
//  and it's easy to individually load additional languages
import go from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.12.0/build/es/languages/go.min.js';
hljs.registerLanguage('go', go);
</script>

unpkg (link)

Common JS
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.12.0/styles/default.min.css">
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.12.0/highlight.min.js"></script>
<!-- and it's easy to individually load additional languages -->
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.12.0/languages/go.min.js"></script>
ES6 Modules
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.12.0/styles/default.min.css">
<script type="module">
import hljs from 'https://unpkg.com/@highlightjs/cdn-assets@11.12.0/es/highlight.min.js';
//  and it's easy to individually load & register additional languages
import go from 'https://unpkg.com/@highlightjs/cdn-assets@11.12.0/es/languages/go.min.js';
hljs.registerLanguage('go', go);
</script>

Note: The CDN-hosted highlight.min.js package doesn't bundle every language. It would be very large. You can find our list of "common" languages that we bundle by default on our download page.

Download prebuilt CDN assets

You can also download and self-host the same assets we serve up via our own CDNs. We publish those builds to the cdn-release GitHub repository. You can easily pull individual files off the CDN endpoints with curl, etc; if say you only needed highlight.min.js and a single CSS file.

There is also an npm package @highlightjs/cdn-assets if pulling the assets in via npm or yarn would be easier for your build process.

Download from our website

The download page can quickly generate a custom single-file minified bundle including only the languages you desire.

Note: Building from source can produce slightly smaller builds than the website download.

Install via NPM package

Our NPM package including all supported languages can be installed with NPM or Yarn:

npm install highlight.js
# or
yarn add highlight.js

There is also another npm package @highlightjs/cdn-assets that contains prebuilt CDN assets including ES6 Modules that can be imported in browser:

npm install @highlightjs/cdn-assets
# or
yarn add @highlightjs/cdn-assets

Alternatively, you can build the NPM package from source.

Build from Source

The current source code is always available on GitHub.

node tools/build.js -t node
node tools/build.js -t browser :common
node tools/build.js -t cdn :common

See our building documentation for more information.

Requirements

Highlight.js works on all modern browsers and currently supported Node.js versions. You'll need the following software to contribute to the core library:

  • Node.js >= 12.x
  • npm >= 6.x

License

Highlight.js is released under the BSD License. See our LICENSE file for details.

Links

The official website for the library is https://highlightjs.org/.

Further in-depth documentation for the API and other topics is at http://highlightjs.readthedocs.io/.

A list of the Core Team and contributors can be found in the CONTRIBUTORS.md file.