react-shiki

A performant syntax-highlighting component and hook for React, powered by Shiki's TextMate grammars and themes.

Library
npm
v0.11.1
538stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
73/100Good
Development Activity76
Maintenance92
Community40
Maturity44
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
78/100Good
Architecture87
Code Quality88
Innovation72
Learning Curve65

react-shiki brings Shiki’s TextMate-grammar-accurate syntax highlighting to React as both a drop-in ShikiHighlighter component and a lower-level useShikiHighlighter hook. It renders highlighted code as real React elements by default (via hast-util-to-jsx-runtime), avoiding dangerouslySetInnerHTML unless an app explicitly opts into HTML string output, and it exposes an experimental raw-tokens output for teams that want to own their own renderer.

The package ships three entry points that trade capability for bundle size: the full bundle includes every Shiki language and theme with zero configuration, the web bundle narrows that down to web-focused languages for a smaller footprint, and the core bundle exposes a bring-your-own-highlighter API for apps that want to dynamically import only the exact languages and themes they use. All three share the same underlying hook implementation, so switching bundles is a near drop-in replacement.

Beyond basic highlighting, react-shiki handles the harder edges of using Shiki inside React: automatic detection and loading of embedded languages (a TypeScript block fenced inside Markdown highlights correctly with no extra config), single/dual/multi-theme support with reactive light-dark switching, custom TextMate themes and grammars, line numbers, and throttled re-highlighting for streamed content such as LLM output where naive re-rendering on every token would be wasteful.

It’s built for React apps and static-site/markdown pipelines that need accurate, themeable code blocks without hand-rolling a Shiki integration or reaching for a heavier syntax-highlighting library.

What You Get

  • A ShikiHighlighter component and a useShikiHighlighter hook, sharing one core implementation, for either declarative or programmatic use
  • Three entry points (react-shiki, react-shiki/web, react-shiki/core) trading bundle size for language/theme coverage, from zero-config full coverage down to a bring-your-own-highlighter minimal core
  • Default output as real React elements (no dangerouslySetInnerHTML), with opt-in HTML string output and an experimental raw-tokens output for custom renderers
  • Automatic detection and on-demand loading of embedded languages, e.g. TypeScript fenced inside a Markdown document
  • Single, dual, or multi-theme support, including CSS light-dark()-based reactive theme switching tied to the user’s color-scheme preference
  • Support for custom TextMate themes and language grammars passed as objects or dynamic imports, plus custom Shiki transformers and decorations
  • Built-in throttling for streamed/rapidly-changing code (e.g. LLM output), with a choice between the Oniguruma WASM engine and a lighter JavaScript RegExp engine
  • Optional line numbers with configurable starting number and per-line highlighting

Common Use Cases

  • Rendering syntax-highlighted code blocks in a documentation site or blog built with a Markdown/MDX pipeline
  • Highlighting streamed code from an LLM chat interface in real time, using throttling to avoid re-highlighting on every token
  • Building a code playground or snippet viewer that needs dual light/dark themes tied to the app’s own theme toggle
  • Rendering fenced code blocks inside react-markdown output, including automatically highlighting embedded languages
  • Shipping a size-conscious production app that only needs a fixed, known set of languages via the core bundle’s bring-your-own-highlighter API

Under The Hood

Architecture The three public entry points (index.ts for the full bundle, web.ts, and core.ts) are thin wrappers that each supply a different highlighter factory to one shared useHighlight hook in lib/hook.ts, so bundle choice is a configuration concern, not a code fork. The hook resolves the requested language, theme, and RegExp engine (lib/language.ts, lib/theme.ts, lib/engine.ts), builds Shiki’s codeToHast/codeToHtml/codeToTokens options (lib/options.ts), lazily creates or reuses a cached highlighter instance, and converts the resulting HAST tree into React elements via hast-util-to-jsx-runtime. The ShikiHighlighter component (lib/component.tsx) is a further thin layer over the hook, adding the container element, language label, and default styles. Named RegExp engines are memoized in a module-level Map in lib/engine.ts so repeated calls reuse the same instance instead of recreating WASM or JS engines on every render, and a throttleHighlighting utility in lib/utils.ts coalesces rapid re-highlighting for streaming use cases. This layering — bundle-specific factory, shared hook, shared component — keeps the three entry points close to drop-in replacements for each other.

Tech Stack Written in TypeScript and built with tsdown (a Rolldown-based bundler) into dual ESM outputs per entry point (index.mjs, web.mjs, core.mjs, plus a css export for default styles). Runtime dependencies are deliberately minimal: shiki itself for grammar/theme data and the highlighting engines, hast-util-to-jsx-runtime and unist-util-visit for HAST-to-React conversion, clsx for class composition, and dequal for cheap deep-equality checks that keep hook inputs referentially stable across renders. React and its DOM renderer are peer dependencies, not bundled. The monorepo (managed with pnpm workspaces and Changesets for release automation) also contains a Vite-based playground app used for manual/visual testing of the component and hook.

Code Quality The package has an extensive Vitest test suite (bundles.test.ts, component.test.tsx, engine.test.ts, engine-integration.test.tsx, hook.test.tsx, language.test.ts, options.test.ts, plugins.test.ts, styles.test.ts, theme.test.ts, types.test-d.tsx, utils.test.tsx) covering both bundle-specific behavior and shared hook/component logic, plus a dedicated type-level test file and a performance benchmark suite. Linting and formatting run through Biome, type-checking through tsc, and a check:package script verifies published package integrity (exports map correctness, arethetypeswrong compatibility). CI (GitHub Actions) runs the build across multiple Node versions per pull request. Source code favors small, single-purpose modules under lib/ and bundles/, explicit TypeScript types for all public options, and comments that explain non-obvious tradeoffs (e.g. why an engine cache exists, why a rejected engine load is evicted) rather than restating the code.

What Makes It Unique Most React syntax-highlighting libraries either bundle a fixed, comprehensive set of languages regardless of what an app actually uses, or hand the integrator a low-level highlighter API with no React ergonomics. react-shiki instead offers a graduated set of three entry points sharing one implementation, letting an app pick the exact point on the bundle-size-versus-configuration-effort curve it needs, down to a bring-your-own-highlighter core bundle for apps that dynamically import only the languages and themes they ship. It also treats streaming output as a first-class use case via built-in throttling and a documented React-versus-HTML output tradeoff, rather than treating LLM-style incremental highlighting as an afterthought, and defaults to real React elements instead of dangerouslySetInnerHTML, avoiding a common XSS footgun in comparable libraries.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search