Shiki is a syntax highlighting library based on TextMate grammars. It transforms source code into accurate, theme-aware HTML, matching the highlighting used in VS Code.
export default function Page() {
return <h1>Hello</h1>
}Installation
shadcn/ui
pnpm dlx shadcn@latest add https://code-blocks.pheralb.dev/r/shiki-highlighter.jsonManual
- Install the following dependencies:
pnpm i shiki -DAs optional, you can also install separate packages for themes and languages:
pnpm i @shikijs/themes @shikijs/langs -D- Create a your shiki utility file:
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
import {
type HighlighterCore,
type RegexEngine,
createHighlighterCore,
} from "shiki/core";
// Themes:
import lightTheme from "@shikijs/themes/one-light";
import darkTheme from "@shikijs/themes/one-dark-pro";
// Languages:
import html from "@shikijs/langs/html";
import js from "@shikijs/langs/js";
import ts from "@shikijs/langs/ts";
import tsx from "@shikijs/langs/tsx";
import css from "@shikijs/langs/css";
import json from "@shikijs/langs/json";
import bash from "@shikijs/langs/bash";
import markdown from "@shikijs/langs/mdx";
let jsEngine: RegexEngine | null = null;
let highlighter: Promise<HighlighterCore> | null = null;
// Settings for UI components
const Themes = {
light: "one-light",
dark: "one-dark-pro",
};
type Languages = "html" | "js" | "ts" | "tsx" | "css" | "bash" | "json" | "mdx";
const getJsEngine = (): RegexEngine => {
jsEngine ??= createJavaScriptRegexEngine();
return jsEngine;
};
const highlight = async (): Promise<HighlighterCore> => {
highlighter ??= createHighlighterCore({
themes: [lightTheme, darkTheme],
langs: [bash, js, ts, tsx, css, markdown, html, json],
engine: getJsEngine(),
});
return highlighter;
};
export { highlight, Themes, type Languages };For better performance, this utility only imports the languages and themes that you will use. If you don't prefer importing specific items, you can use shiki/bundle/web directly:
import {
type Highlighter,
type RegexEngine,
createHighlighter,
} from "shiki/bundle/web";
let highlighter: Promise<Highlighter> | null = null;
// ...
const highlight = async (): Promise<Highlighter> => {
highlighter ??= createHighlighter({
langs: ["html", "js", "ts", "tsx", "css", "bash", "mdx"],
themes: ["one-light", "one-dark-pro"],
engine: getJsEngine(),
});
return highlighter;
};This utility exports:
highlight(): A function that initializes and returns a Shiki highlighter instance with the specified theme and languages.Themes: A type with the available themes. You can use it in your UI components to type thethemeprop.Languages: A type with the available languages. You can use it in your UI components to type thelanguageprop.
Styles
This CSS file includes light/dark mode and all transformers styles:
/* Tailwind CSS */
@import "./globals.css";
/* Shiki Light/Dark Mode */
html.light .shiki,
html.light .shiki span {
font-family: var(--font-mono);
background-color: transparent !important;
}
html.dark .shiki,
html.dark .shiki span {
font-family: var(--font-mono);
color: var(--shiki-dark) !important;
background-color: transparent !important;
}
/* Base Shiki Pre & Span Styles */
pre.shiki {
@apply py-3;
}
pre.shiki span.line {
@apply px-4 py-0.5;
}
/* Shiki Word Wrap */
pre.shiki-word-wrap {
white-space: pre-wrap;
word-break: break-word;
}
pre.shiki-word-wrap span.line {
display: inline-block;
width: 100%;
box-sizing: border-box;
padding-top: 0.2px;
padding-bottom: 0.2px;
}
/* Shiki Line Numbers */
pre.shiki-line-numbers code {
counter-reset: step;
counter-increment: step 0;
}
pre.shiki-line-numbers code:has(.line:nth-child(2)) .line::before {
content: counter(step);
counter-increment: step;
width: 0.5rem;
margin-right: 1.3rem;
margin-left: 0.2rem;
display: inline-block;
@apply text-right font-mono text-xs text-neutral-500;
}
/* Shiki Highlight */
pre span.shiki-line-highlight {
@apply relative z-0 inline-block w-full;
&::after {
content: "";
@apply absolute top-0 left-0 -z-10 h-full w-full border-l-2 border-neutral-400 bg-neutral-500/20! opacity-40;
}
}
/* Shiki Notation Diff */
pre.has-diff span.line.diff {
@apply relative inline-block w-full;
}
pre.has-diff span.line.diff.add {
@apply bg-green-300/20 dark:bg-green-700/20;
&::before {
content: "+";
@apply absolute left-2 text-green-600 dark:text-green-400;
}
}
pre.has-diff span.line.diff.remove {
@apply bg-red-300/20 opacity-70 dark:bg-red-600/20;
&::before {
content: "-";
@apply absolute left-2 text-red-600 dark:text-red-400;
}
}
/* Shiki Notation Focus */
pre.shiki-has-focused .line:not(.focused) {
@apply opacity-50 blur-[0.8px] transition-opacity duration-200 ease-in-out;
}
pre.shiki-has-focused:hover .line:not(.focused) {
@apply opacity-100 blur-none;
}
/* Shiki Line Anchors */
pre.shiki-line-anchors {
scroll-margin-top: 2rem;
}
pre.shiki-line-anchors .line:target {
@apply relative inline-block w-full bg-blue-300/15 dark:bg-blue-500/15;
&::before {
content: "";
@apply absolute left-0 top-0 h-full w-1 bg-blue-500 dark:bg-blue-400;
}
}
pre.shiki-line-numbers.shiki-line-anchors code .line::before {
@apply cursor-pointer select-none transition-colors;
}
pre.shiki-line-numbers.shiki-line-anchors code .line::before:hover {
@apply text-blue-500 underline dark:text-blue-400;
}Usage
You can use the highlight function to get syntax highlighted code. For example:
import { highlight } from "@/utils/shiki";
const code = `console.log('hello')`;
const highlighter = await highlight();
const html = highlighter.codeToHtml(code, {
lang: "javascript",
theme: "one-light",
});Themes
There are multiple themes available in the @shikijs/themes package. You can find the full list of themes in the Shiki Themes documentation.
If you need to use a different theme:
- Update the import statements in the
shiki-highlighter.tsfile:
// Themes:
import lightTheme from "@shikijs/themes/one-light"; // Light Theme
import darkTheme from "@shikijs/themes/one-dark-pro"; // Dark Theme- Update the
Themestype:
type Themes = "one-light" | "one-dark-pro";Languages
There are multiple languages available in the @shikijs/langs package. You can find the full list of languages in the Shiki Languages documentation.
If you need to use different languages:
- Update the import statements in the
shiki-highlighter.tsfile:
// Languages:
import js from "@shikijs/langs/js";
import ts from "@shikijs/langs/ts";
import tsx from "@shikijs/langs/tsx";
//...- Update the
Languagestype:
type Languages = "js" | "ts" | "tsx" | ... ;Markdown/MDX Integration
- Install the
@shikijs/rehypepackage:
pnpm i @shikijs/rehype -D- Create a
rehypeShikiOptionsutility to configure the MDX plugin:
import type { RehypeShikiCoreOptions } from "@shikijs/rehype/core";
const rehypeShikiOptions: RehypeShikiCoreOptions = {
themes: {
light: "one-light",
dark: "one-dark-pro",
},
};
export { rehypeShikiOptions };- Use the
highlightandrehypeShikiOptionsin your MDX setup as Rehype plugin:
import rehypeShiki from "@shikijs/rehype/core";
import { highlight } from "@/utils/shiki-highlighter";
import { rehypeShikiOptions } from "@/utils/rehype-shiki-options";
const highlighter = await highlight();
const mdx = await compileMDX(context, document, {
rehypePlugins: [[rehypeShiki, highlighter, rehypeShikiOptions]],
});