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.
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.
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' });
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' });
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 } };
}
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
Despite their differences, these libraries share common goals and some overlapping capabilities.
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' })
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'
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)
| Feature | highlight.js | prismjs | react-syntax-highlighter | shiki |
|---|---|---|---|---|
| Detection | ✅ Auto-detect | ❌ Explicit only | ❌ Explicit prop | ❌ Explicit only |
| Theme Engine | CSS Classes | CSS Classes | CSS Classes | VS Code JSON |
| Execution | Browser (Runtime) | Browser (Runtime) | Browser (Runtime) | Node/Build-time |
| Plugins | Limited | Extensive | Wrapped Plugins | None (DIY) |
| Best For | Blogs, Auto-lang | Docs, Features | React Apps | Design Systems |
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.
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.
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.
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.
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.
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
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.
Please see SECURITY.md for long-term support information.
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>
To apply the Highlight.js styling to plaintext without actually highlighting it, use the plaintext language:
<pre><code class="language-plaintext">...</code></pre>
To skip highlighting of a code block completely, use the nohighlight class:
<pre><code class="nohighlight">...</code></pre>
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.
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.
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.
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;
}
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>
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);
};
First, you'll likely be installing the library via npm or yarn -- see Getting the Library.
requireRequiring 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
importThe 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';
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.
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
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.
<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>
<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>
<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>
<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>
<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>
<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.
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.
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.
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.
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.
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:
Highlight.js is released under the BSD License. See our LICENSE file for details.
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.