Shiki vs Prism vs highlight.js: Syntax Highlighting Libraries (2026)
TL;DR
Shiki uses VS Code's TextMate grammars — accurate, beautiful highlighting with VS Code themes, renders to HTML at build time, no client-side JavaScript needed, used by VitePress, Astro, and Nuxt Content. Prism is the lightweight client-side highlighter — extensible plugin system, token-based, 300+ languages, used by MDN and Gatsby. highlight.js is the classic auto-detecting highlighter — automatically detects language, 190+ languages, no dependencies, the most widely used. In 2026: Shiki for static/server-rendered docs, Prism for client-side with plugins, highlight.js for zero-config highlighting.
Key Takeaways
- Shiki: ~5M weekly downloads — VS Code grammars, server-side, zero client JS
- Prism: ~5M weekly downloads — lightweight, plugins (line numbers, copy), client-side
- highlight.js: ~10M weekly downloads — auto-detect language, zero-config, universal
- Shiki produces the most accurate highlighting (same engine as VS Code)
- Prism has the best plugin ecosystem (line highlighting, diff, toolbar)
- highlight.js is the easiest to set up (script tag + auto-detection)
Shiki
Shiki — VS Code-powered highlighting:
Basic usage
import { codeToHtml } from "shiki"
const html = await codeToHtml('console.log("Hello, World!")', {
lang: "javascript",
theme: "one-dark-pro",
})
console.log(html)
// → <pre class="shiki one-dark-pro" style="background-color:#282c34">
// → <code><span style="color:#E5C07B">console</span>...
// → </pre>
// Output is fully styled HTML — no client-side JS needed!
Multiple themes (light/dark)
import { codeToHtml } from "shiki"
// Dual theme — light + dark mode:
const html = await codeToHtml('const x = 42', {
lang: "typescript",
themes: {
light: "github-light",
dark: "github-dark",
},
})
// CSS to switch between themes:
// @media (prefers-color-scheme: dark) {
// .shiki { background-color: var(--shiki-dark-bg) !important; }
// .shiki span { color: var(--shiki-dark) !important; }
// }
Highlighter instance (performance)
import { createHighlighter } from "shiki"
// Create reusable highlighter (loads grammars once):
const highlighter = await createHighlighter({
themes: ["one-dark-pro", "github-light"],
langs: ["javascript", "typescript", "python", "rust", "json"],
})
// Fast repeated highlighting:
const html1 = highlighter.codeToHtml('const x = 1', { lang: "ts", theme: "one-dark-pro" })
const html2 = highlighter.codeToHtml('def main():', { lang: "python", theme: "one-dark-pro" })
const html3 = highlighter.codeToHtml('fn main() {}', { lang: "rust", theme: "one-dark-pro" })
// Dispose when done:
highlighter.dispose()
Transformers (line highlighting, diff, etc.)
import { codeToHtml } from "shiki"
import {
transformerNotationDiff,
transformerNotationHighlight,
transformerNotationFocus,
} from "@shikijs/transformers"
const code = `
const old = "removed" // [!code --]
const new = "added" // [!code ++]
const highlighted = 1 // [!code highlight]
const focused = true // [!code focus]
`
const html = await codeToHtml(code, {
lang: "typescript",
theme: "one-dark-pro",
transformers: [
transformerNotationDiff(), // Red/green diff lines
transformerNotationHighlight(), // Highlighted lines
transformerNotationFocus(), // Focus with dimmed context
],
})
Framework integrations
// VitePress — built-in Shiki:
// ```ts {1,3-4} ← line highlighting
// const a = 1 // [!code ++] ← diff
// ```
// Astro — built-in Shiki:
// astro.config.mjs
export default defineConfig({
markdown: {
shikiConfig: {
theme: "one-dark-pro",
wrap: true,
},
},
})
// Nuxt Content — built-in Shiki:
// nuxt.config.ts
export default defineNuxtConfig({
content: {
highlight: {
theme: "github-dark",
langs: ["js", "ts", "vue", "css", "html"],
},
},
})
Prism
Prism — lightweight client-side highlighter:
Basic setup
<!-- CDN (quickest setup): -->
<link href="https://cdn.jsdelivr.net/npm/prismjs/themes/prism-tomorrow.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/prismjs/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs/components/prism-typescript.min.js"></script>
<!-- Your code blocks: -->
<pre><code class="language-typescript">
const greeting: string = "Hello, World!"
console.log(greeting)
</code></pre>
<!-- Prism auto-highlights on page load -->
Node.js / programmatic
import Prism from "prismjs"
import "prismjs/components/prism-typescript"
import "prismjs/components/prism-python"
import "prismjs/components/prism-rust"
const code = 'const x: number = 42'
const html = Prism.highlight(code, Prism.languages.typescript, "typescript")
console.log(html)
// → <span class="token keyword">const</span>
// → <span class="token literal-property">x</span>
// → <span class="token operator">:</span> ...
// Wrap in <pre><code>:
const block = `<pre class="language-typescript"><code>${html}</code></pre>`
Plugins
<!-- Line numbers: -->
<script src="https://cdn.jsdelivr.net/npm/prismjs/plugins/line-numbers/prism-line-numbers.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/prismjs/plugins/line-numbers/prism-line-numbers.css" rel="stylesheet" />
<pre class="line-numbers"><code class="language-js">...</code></pre>
<!-- Line highlight: -->
<script src="https://cdn.jsdelivr.net/npm/prismjs/plugins/line-highlight/prism-line-highlight.min.js"></script>
<pre data-line="2,4-6"><code class="language-js">...</code></pre>
<!-- Copy to clipboard: -->
<script src="https://cdn.jsdelivr.net/npm/prismjs/plugins/toolbar/prism-toolbar.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script>
<!-- Diff highlight: -->
<pre><code class="language-diff-typescript diff-highlight">
- const old = "removed"
+ const new = "added"
</code></pre>
Themes
Built-in themes:
prism.css — Default (light)
prism-dark.css — Dark
prism-funky.css — Funky
prism-okaidia.css — Okaidia (Monokai)
prism-twilight.css — Twilight
prism-coy.css — Coy
prism-solarizedlight.css — Solarized Light
prism-tomorrow.css — Tomorrow Night
Community themes (prism-themes package):
prism-vsc-dark-plus — VS Code Dark+
prism-one-dark — Atom One Dark
prism-dracula — Dracula
prism-nord — Nord
prism-material-dark — Material Dark
highlight.js
highlight.js — auto-detecting highlighter:
Basic setup
<!-- CDN (simplest possible setup): -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js/styles/github-dark.min.css" />
<script src="https://cdn.jsdelivr.net/npm/highlight.js/highlight.min.js"></script>
<script>hljs.highlightAll()</script>
<!-- Code blocks — language auto-detected! -->
<pre><code>
const greeting = "Hello, World!"
console.log(greeting)
</code></pre>
<!-- highlight.js detects this as JavaScript automatically -->
Node.js / programmatic
import hljs from "highlight.js"
// Auto-detect language:
const result = hljs.highlightAuto('const x = 42')
console.log(result.language) // → "javascript"
console.log(result.value) // → highlighted HTML
// Specify language:
const result2 = hljs.highlight('const x: number = 42', { language: "typescript" })
console.log(result2.value)
// → <span class="hljs-keyword">const</span>
// → <span class="hljs-attr">x</span>: ...
// Only load specific languages (smaller bundle):
import hljs from "highlight.js/lib/core"
import javascript from "highlight.js/lib/languages/javascript"
import typescript from "highlight.js/lib/languages/typescript"
hljs.registerLanguage("javascript", javascript)
hljs.registerLanguage("typescript", typescript)
Themes
Built-in themes (300+):
Popular dark themes:
github-dark.css
atom-one-dark.css
vs2015.css — VS Code dark
monokai.css
dracula.css
nord.css
tokyo-night-dark.css
Popular light themes:
github.css
atom-one-light.css
vs.css — VS Code light
stackoverflow-light.css
xcode.css
// Use theme:
<link rel="stylesheet" href="highlight.js/styles/github-dark.css" />
Language auto-detection
import hljs from "highlight.js"
// Auto-detect works by scoring each language:
const results = [
hljs.highlightAuto('print("hello")'), // → python
hljs.highlightAuto('console.log("hello")'), // → javascript
hljs.highlightAuto('fn main() { println!("hello"); }'), // → rust
hljs.highlightAuto('SELECT * FROM users'), // → sql
]
results.forEach((r) => {
console.log(`${r.language}: confidence ${r.relevance}`)
})
// Limit detection to specific languages:
const result = hljs.highlightAuto(code, ["javascript", "typescript", "python"])
With Markdown renderers
import MarkdownIt from "markdown-it"
import hljs from "highlight.js"
const md = new MarkdownIt({
highlight(str, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(str, { language: lang }).value
}
return hljs.highlightAuto(str).value
},
})
const html = md.render("```typescript\nconst x = 42\n```")
Feature Comparison
| Feature | Shiki | Prism | highlight.js |
|---|---|---|---|
| Engine | TextMate (VS Code) | Custom tokenizer | Custom parser |
| Rendering | Server-side (HTML) | Client-side (DOM) | Client/server |
| Client JS needed | ❌ | ✅ | ✅ |
| Language auto-detect | ❌ | ❌ | ✅ |
| Languages | 200+ | 300+ | 190+ |
| Themes | VS Code themes | CSS themes | 300+ CSS themes |
| Light/dark toggle | ✅ (CSS vars) | ✅ (swap CSS) | ✅ (swap CSS) |
| Line numbers | Via transformer | ✅ (plugin) | ✅ (plugin) |
| Line highlighting | ✅ (transformer) | ✅ (plugin) | ❌ |
| Diff highlighting | ✅ (transformer) | ✅ (plugin) | ✅ |
| Copy button | Via transformer | ✅ (plugin) | ❌ |
| Accuracy | Highest (VS Code) | Good | Good |
| Bundle size | 0 (server-side) | ~20KB + langs | ~30KB + langs |
| Used by | VitePress, Astro | MDN, Gatsby | Many CMS/blogs |
| Weekly downloads | ~5M | ~5M | ~10M |
When to Use Each
Use Shiki if:
- Building a static site or documentation (VitePress, Astro, Nuxt)
- Want the most accurate highlighting (VS Code quality)
- Need zero client-side JavaScript
- Want VS Code themes (One Dark Pro, GitHub Dark, etc.)
- Need line highlighting, diff, or focus transformers
Use Prism if:
- Need client-side highlighting with plugins
- Want line numbers, copy button, toolbar out of the box
- Building a CMS or user-facing code editor
- Need the largest language support (300+ languages)
- Prefer CSS-based theming
Use highlight.js if:
- Want the simplest setup (script tag + auto-highlight)
- Need automatic language detection
- Building a blog or content site with user-submitted code
- Want the largest theme collection
- Need Markdown renderer integration
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on Shiki v1.x, Prism v1.x, and highlight.js v11.x.
Compare developer tooling and documentation utilities on PkgPulse →