react-shiki 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,10 +2,8 @@
2
2
 
3
3
  A performant client-side syntax highlighting component and hook for React, built with [Shiki](https://shiki.matsu.io/).
4
4
 
5
- [See the demo page with highlighted code blocks showcasing several Shiki themes!](https://react-shiki.vercel.app/)
6
-
7
5
  <!--toc:start-->
8
- - [🎨 react-shiki](#🎨-react-shiki)
6
+ - [🎨 react-shiki](#-react-shiki)
9
7
  - [Features](#features)
10
8
  - [Installation](#installation)
11
9
  - [Usage](#usage)
@@ -14,11 +12,14 @@ A performant client-side syntax highlighting component and hook for React, built
14
12
  - [`react-shiki/web` (Web Bundle)](#react-shikiweb-web-bundle)
15
13
  - [`react-shiki/core` (Minimal Bundle)](#react-shikicore-minimal-bundle)
16
14
  - [RegExp Engines](#regexp-engines)
17
- - [Using Engines with Full and Web Bundles](#using-engines-with-full-and-web-bundles)
18
- - [Using Engines with Core Bundle](#using-engines-with-core-bundle)
19
- - [Engine Options](#engine-options)
15
+ - [Full and Web Bundles](#full-and-web-bundles)
16
+ - [Custom Highlighters (Core Bundle)](#custom-highlighters-core-bundle)
17
+ - [JavaScript Raw Engine](#javascript-raw-engine)
18
+ - [Output Formats](#output-formats)
20
19
  - [Configuration](#configuration)
21
20
  - [Common Configuration Options](#common-configuration-options)
21
+ - [react-shiki Options](#react-shiki-options)
22
+ - [Shiki Pass-through Options](#shiki-pass-through-options)
22
23
  - [Component-specific Props](#component-specific-props)
23
24
  - [Embedded Language Highlighting](#embedded-language-highlighting)
24
25
  - [Multi-theme Support](#multi-theme-support)
@@ -36,7 +37,6 @@ A performant client-side syntax highlighting component and hook for React, built
36
37
  - [Handling Inline Code](#handling-inline-code)
37
38
  - [Performance](#performance)
38
39
  - [Throttling Real-time Highlighting](#throttling-real-time-highlighting)
39
- - [Output Formats](#output-formats)
40
40
  <!--toc:end-->
41
41
 
42
42
  ## Features
@@ -44,7 +44,7 @@ A performant client-side syntax highlighting component and hook for React, built
44
44
  - 🖼️ Provides both a `ShikiHighlighter` component and a `useShikiHighlighter` hook for more flexibility
45
45
  - 🔐 Flexible output: Choose between React elements (no `dangerouslySetInnerHTML`) or HTML strings
46
46
  - 📦 Multiple bundle options: Full bundle (~1.2MB gz), web bundle (~707KB gz), or minimal core bundle for fine-grained bundle control
47
- - 🖌️ Full support for custom TextMate themes and languages
47
+ - 🖌️ Full support for custom TextMate themes and languages, passed as objects or dynamic imports
48
48
  - 🧬 Automatic highlighting of embedded languages (e.g. TypeScript fenced inside Markdown) via Shiki's `guessEmbeddedLanguages`
49
49
  - 🔧 Supports passing custom Shiki transformers to the highlighter, in addition to all other options supported by `codeToHast`
50
50
  - 🚰 Performant highlighting of streamed code, with optional throttling
@@ -98,8 +98,8 @@ function CodeBlock({ code, language }) {
98
98
  ```tsx
99
99
  import ShikiHighlighter from 'react-shiki';
100
100
  ```
101
- - **Size**: ~6.4MB minified, ~1.2MB gzipped (includes ~12KB react-shiki)
102
- - **Languages**: All Shiki languages and themes
101
+ - **Size**: ~6.4MB minified, ~1.2MB gzipped (react-shiki itself: ~9KB minified, ~4KB gzipped)
102
+ - **Languages**: All Shiki [languages](https://shiki.style/languages) and [themes](https://shiki.style/themes)
103
103
  - **Exported engines**: `createJavaScriptRegexEngine`, `createJavaScriptRawEngine`
104
104
  - **Use case**: Unknown language requirements, maximum language support
105
105
  - **Setup**: Zero configuration required
@@ -108,8 +108,8 @@ import ShikiHighlighter from 'react-shiki';
108
108
  ```tsx
109
109
  import ShikiHighlighter from 'react-shiki/web';
110
110
  ```
111
- - **Size**: ~3.8MB minified, ~707KB gzipped (includes ~12KB react-shiki)
112
- - **Languages**: Web-focused languages (HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte)
111
+ - **Size**: ~3.8MB minified, ~707KB gzipped (react-shiki itself: ~9KB minified, ~4KB gzipped)
112
+ - **Languages**: [Web-focused languages](https://shiki.style/guide/bundles#shiki-bundle-web) (HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte)
113
113
  - **Exported engines**: `createJavaScriptRegexEngine`, `createJavaScriptRawEngine`
114
114
  - **Use case**: Web applications with balanced size/functionality
115
115
  - **Setup**: Drop-in replacement for main entry point
@@ -134,7 +134,7 @@ const highlighter = await createHighlighterCore({
134
134
  {code}
135
135
  </ShikiHighlighter>
136
136
  ```
137
- - **Size**: ~12KB + your imported themes/languages
137
+ - **Size**: ~9KB minified (~4KB gzipped) + your imported themes/languages
138
138
  - **Languages**: User-defined via custom highlighter
139
139
  - **Use case**: Production apps requiring maximum bundle control
140
140
  - **Setup**: Requires custom highlighter configuration
@@ -143,40 +143,57 @@ const highlighter = await createHighlighterCore({
143
143
  ### RegExp Engines
144
144
 
145
145
  Shiki offers three built-in engines for syntax highlighting:
146
- - **Oniguruma** - Default engine using compiled WebAssembly, offers maximum language support
147
- - **JavaScript RegExp** - Smaller bundle, faster startup, compiles patterns on-the-fly, recommended for client-side highlighting
148
- - **JavaScript Raw** - For [pre-compiled languages](https://shiki.style/guide/regex-engines#pre-compiled-languages), skips transpilation step for best performance
149
146
 
150
- #### Using Engines with Full and Web Bundles
147
+ | Engine | Payload (gzip) | Startup | Grammar support |
148
+ | --- | --- | --- | --- |
149
+ | **Oniguruma** (default) | ~150 KB (466 KB WASM) | fetch + compile WASM | Reference engine, all grammars |
150
+ | **JavaScript RegExp** | ~20 KB | none, plain JS | All built-in languages ([compatibility table](https://shiki.style/references/engine-js-compat)); [strict by default](#full-and-web-bundles) |
151
+ | **JavaScript Raw** | ~1 KB | none | [Pre-compiled grammars](https://shiki.style/guide/regex-engines#pre-compiled-languages) only, has a [known bug](https://github.com/shikijs/shiki/issues/918) |
151
152
 
152
- The full and web bundles use Oniguruma by default, but you can override this with the `engine` option:
153
+ Payload sizes measured against shiki 4.3.0: `onig.wasm` is 466 KB (149 KB gzipped), the JavaScript engine's pattern transpiler ([oniguruma-to-es](https://github.com/slevithan/oniguruma-to-es)) is 57 KB (20 KB gzipped), and the raw engine skips the transpiler entirely. Beyond the smaller download, the JavaScript engine also starts instantly (no WASM fetch and compile) and [runs patterns as native JavaScript RegExp](https://shiki.style/guide/regex-engines#javascript-regexp-engine), which is faster for some languages.
153
154
 
154
- ```tsx
155
- import {
156
- useShikiHighlighter,
157
- createJavaScriptRegexEngine,
158
- createJavaScriptRawEngine
159
- } from 'react-shiki';
155
+ #### Full and Web Bundles
156
+
157
+ The full and web bundles use Oniguruma by default. You can swap to the JavaScript engine by passing `'javascript'` to `engine`. react-shiki creates and caches the engine internally, no need to import it, no WASM fetch with `'javascript'`, and it's safe to pass inline:
160
158
 
161
- // Hook with JavaScript RegExp engine
159
+ ```tsx
160
+ // Hook
162
161
  const highlightedCode = useShikiHighlighter(code, 'typescript', 'github-dark', {
163
- engine: createJavaScriptRegexEngine()
162
+ engine: 'javascript'
164
163
  });
165
164
 
166
- // Component with JavaScript Raw engine (for pre-compiled languages)
167
- // See https://shiki.style/guide/regex-engines#pre-compiled-languages
168
- <ShikiHighlighter
169
- language="typescript"
170
- theme="github-dark"
171
- engine={createJavaScriptRawEngine()}
172
- >
165
+ // Component
166
+ <ShikiHighlighter language="typescript" theme="github-dark" engine="javascript">
173
167
  {code}
174
168
  </ShikiHighlighter>
175
169
  ```
176
170
 
177
- #### Using Engines with Core Bundle
171
+ > [!NOTE]
172
+ > The underlying highlighter is a singleton created on the first highlight, so pass the same engine at every call site; mixed engines resolve to whichever highlighted first.
178
173
 
179
- When using the core bundle, you must specify an engine:
174
+ To customize engine configuration beyond the named defaults, pass an engine instance. **Create it once at module scope**; this avoids recreating new instances on every render, which would defeat memoization:
175
+
176
+ ```tsx
177
+ import { useShikiHighlighter, createJavaScriptRegexEngine } from 'react-shiki';
178
+
179
+ // Ensure this is at the module scope: created once, stable across renders
180
+ const engine = createJavaScriptRegexEngine({ forgiving: true });
181
+
182
+ const highlightedCode = useShikiHighlighter(code, 'typescript', 'github-dark', {
183
+ engine
184
+ });
185
+ ```
186
+
187
+ The JavaScript engine is strict by default: it throws when a grammar uses patterns it can't convert. `forgiving: true` skips those patterns for best-effort output. It also accepts `target` and `regexConstructor`, see [Shiki - RegExp Engines](https://shiki.style/guide/regex-engines) for details.
188
+
189
+ The JavaScript Raw engine works the same way, but only with [pre-compiled languages](https://shiki.style/guide/regex-engines#pre-compiled-languages) (`@shikijs/langs-precompiled`). The full and web bundles don't support the raw engine because they bundle standard grammars, you need a [custom highlighter](#custom-highlighters-core-bundle).
190
+
191
+ > [!IMPORTANT]
192
+ > **Heads up:** in a future release, react-shiki's default engine will be swapped from Oniguruma WASM to the JavaScript engine (with [`forgiving`](https://shiki.style/guide/regex-engines#use-with-unsupported-languages) enabled by default).
193
+
194
+ #### Custom Highlighters (Core Bundle)
195
+
196
+ Named engines (`'javascript'`, `'oniguruma'`) don't apply to the core bundle since you create the highlighter with `createHighlighterCore`; instead, use `createJavaScriptRegexEngine` or `createOnigurumaEngine` to create a custom engine instance:
180
197
 
181
198
  ```tsx
182
199
  import {
@@ -192,41 +209,130 @@ const highlighter = await createHighlighterCore({
192
209
  });
193
210
  ```
194
211
 
195
- #### Engine Options
212
+ ##### JavaScript Raw Engine
196
213
 
197
- The JavaScript RegExp engine is [strict by default](https://shiki.style/guide/regex-engines#use-with-unsupported-languages). For best-effort results with unsupported grammars, enable the `forgiving` option:
214
+ The JavaScript Raw engine is the smallest option at only ~1 KB, but requires [pre-compiled languages](https://shiki.style/guide/regex-engines#pre-compiled-languages), and faces a known upstream bug -- [shikijs/shiki:issue#918](https://github.com/shikijs/shiki/issues/918) -- causing some languages (Python, HTML, Perl, and YAML among those reported) to highlight incorrectly.
198
215
 
199
216
  ```tsx
200
- createJavaScriptRegexEngine({ forgiving: true });
217
+ import {
218
+ useShikiHighlighter,
219
+ createHighlighterCore,
220
+ createJavaScriptRawEngine,
221
+ } from 'react-shiki/core';
222
+
223
+ // Module scope: raw engine + pre-compiled grammars, smallest and fastest
224
+ const highlighter = await createHighlighterCore({
225
+ themes: [import('@shikijs/themes/github-dark')],
226
+ langs: [import('@shikijs/langs-precompiled/typescript')],
227
+ engine: createJavaScriptRawEngine()
228
+ });
229
+
230
+ const highlightedCode = useShikiHighlighter(code, 'typescript', 'github-dark', {
231
+ highlighter
232
+ });
201
233
  ```
202
234
 
203
- See [Shiki - RegExp Engines](https://shiki.style/guide/regex-engines) for more info.
235
+ Be sure to verify your language highlights correctly if you choose to use the raw engine ([shikijs/shiki:issue#918](https://github.com/shikijs/shiki/issues/918)).
236
+
237
+
238
+ ## Output Formats
239
+
240
+ `react-shiki` can return highlighted code in three formats:
241
+
242
+ **React Nodes (Default)** - Rendered as React elements, no `dangerouslySetInnerHTML` required
243
+ ```tsx
244
+ // Hook
245
+ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark");
246
+
247
+ // Component
248
+ <ShikiHighlighter language="tsx" theme="github-dark">
249
+ {code}
250
+ </ShikiHighlighter>
251
+ ```
252
+
253
+ **HTML String** - Rendered via `dangerouslySetInnerHTML`
254
+ ```tsx
255
+ // Hook (returns HTML string, use dangerouslySetInnerHTML to render)
256
+ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
257
+ outputFormat: 'html'
258
+ });
259
+
260
+ // Component (automatically uses dangerouslySetInnerHTML when outputFormat is 'html')
261
+ <ShikiHighlighter language="tsx" theme="github-dark" outputFormat="html">
262
+ {code}
263
+ </ShikiHighlighter>
264
+ ```
265
+
266
+ **Shiki Tokens (Experimental)** - Hook-only output (bring your own renderer, react-shiki does not provide one)
267
+ ```tsx
268
+ const highlighted = useShikiHighlighter(code, "tsx", "github-dark", {
269
+ outputFormat: "tokens",
270
+ });
271
+
272
+ // Sample only, not a production renderer: you own rendering, memoization,
273
+ // and commit management (critical for streaming highlighting)
274
+ const rendered = highlighted?.tokens.map((line, i) => (
275
+ <div key={i}>
276
+ {line.map((token, j) => (
277
+ <span key={j} style={{ color: token.color }}>
278
+ {token.content}
279
+ </span>
280
+ ))}
281
+ </div>
282
+ ));
283
+ ```
284
+
285
+ The React and HTML formats spend their rendering work in different phases. HTML output skips per-token React element creation and reconciliation, reducing render-phase overhead for large, one-shot highlights, but each update replaces the code block's entire DOM subtree via `innerHTML`. React output pays for element creation and diffing on every update, but commits only incremental DOM mutations, minimizing DOM churn for frequently re-highlighted code such as streaming LLM output.
286
+
287
+ HTML output hands the highlighted markup to the DOM via `dangerouslySetInnerHTML`, so only use it with trusted code sources. The default React output is the safe choice for untrusted content.
288
+
289
+ Token output is for when you need to own rendering yourself; it is intentionally available on the hook, not the `ShikiHighlighter` component, since react-shiki's markup-based features no longer apply once you control the markup. Note that Shiki's `codeToTokens` does not run transformers or decorations, so markup-producing options, including transformers, decorations, line numbers, and `structure`, have no effect on token output.
204
290
 
205
291
  ## Configuration
206
292
 
207
293
  ### Common Configuration Options
208
294
 
209
- | Option | Type | Default | Description |
295
+ The core inputs, positional arguments on the hook, props/children on the component:
296
+
297
+ | Input | Type | Default | Description |
210
298
  | ------------------- | ------------------ | --------------- | ----------------------------------------------------------------------------- |
211
299
  | `code` | `string` | - | Code to highlight |
212
300
  | `language` | `string \| object` | - | Language to highlight, built-in or custom TextMate grammar object |
213
301
  | `theme` | `string \| object` | `'github-dark'` | Single or multi-theme configuration, built-in or custom TextMate theme object |
214
- | `delay` | `number` | `0` | Delay between highlights (in milliseconds) |
302
+
303
+ #### react-shiki Options
304
+
305
+
306
+ | Option | Type | Default | Description |
307
+ | ------------------- | ------------------ | --------------- | ----------------------------------------------------------------------------- |
308
+ | `delay` | `number` | - | Minimum time between highlights (in milliseconds), no throttling by default |
309
+ | `engine` | `'javascript' \| 'oniguruma' \| RegexEngine` | `'oniguruma'` | RegExp engine for syntax highlighting; named engines are created and cached internally |
310
+ | `highlighter` | `Highlighter \| HighlighterCore` | - | Custom highlighter instance, required for the core bundle |
311
+ | `outputFormat` | `string` | `'react'` | Output format: 'react' for React nodes, 'html' for HTML string, or 'tokens' for Shiki tokens (hook only, experimental) |
312
+ | `preloadLanguages` | `array` | `[]` | Preload bundled language IDs, custom grammar objects, or dynamic grammar imports |
215
313
  | `customLanguages` | `array` | `[]` | **Deprecated**: use `preloadLanguages` instead. |
216
- | `preloadLanguages` | `array` | `[]` | Preload bundled language IDs and custom language grammars |
217
- | `langAlias` | `object` | `{}` | Map of language aliases |
218
- | `engine` | `RegexEngine` | Oniguruma | RegExp engine for syntax highlighting (Oniguruma, JavaScript RegExp, or JavaScript Raw) |
219
314
  | `showLineNumbers` | `boolean` | `false` | Display line numbers alongside code |
220
315
  | `startingLineNumber` | `number` | `1` | Starting line number when line numbers are enabled |
221
316
  | `highlightLineNumbers` | `number[]` | - | Displayed line numbers to highlight |
222
- | `transformers` | `array` | `[]` | Custom Shiki transformers for modifying the highlighting output |
223
- | `cssVariablePrefix` | `string` | `'--shiki'` | Prefix for CSS variables storing theme colors |
224
- | `defaultColor` | `string \| false` | `'light'` | Default theme mode when using multiple themes, can also disable default theme |
225
- | `outputFormat` | `string` | `'react'` | Output format: 'react' for React nodes, 'html' for HTML string, or 'tokens' for Shiki tokens (hook only, experimental) |
226
- | `tabindex` | `number` | `0` | Tab index for the code block |
227
- | `decorations` | `array` | `[]` | Custom decorations to wrap the highlighted tokens with |
228
- | `structure` | `string` | `'classic'` | The structure of the generated HAST and HTML - `classic` or `inline` |
229
- | [`codeToHastOptions`](https://github.com/shikijs/shiki/blob/main/packages/types/src/options.ts#L121) | - | - | All other options supported by Shiki's `codeToHast` |
317
+
318
+ #### Shiki Pass-through Options
319
+
320
+ All of Shiki's options pass-through and are accessible in react-shiki.
321
+
322
+ Based on `outputFormat`:
323
+ - `'tokens'`: All options supported by Shiki's [`codeToTokens`](https://github.com/shikijs/shiki/blob/v4.3.0/packages/types/src/tokens.ts#L27)
324
+ - `'html'`: All options supported by Shiki's [`codeToHtml`](https://github.com/shikijs/shiki/blob/v4.3.0/packages/types/src/options.ts#L136)
325
+ - `'react'`: All options supported by Shiki's [`codeToHast`](https://github.com/shikijs/shiki/blob/v4.3.0/packages/types/src/options.ts#L136)
326
+
327
+ Commonly used:
328
+ - `transformers`: modify the highlighting output with Shiki transformers (default: `[]`)
329
+ - `decorations`: wrap ranges of highlighted tokens (default: `[]`)
330
+ - `defaultColor`: default theme mode with multi-theme, `false` to disable (default: `'light'`)
331
+ - `cssVariablePrefix`: prefix for theme color CSS variables (default: `'--shiki-'`)
332
+ - `langAlias`: map custom language names to bundled ones (default: `{}`)
333
+ - `structure`: `'classic'` or `'inline'` HAST/HTML shape (default: `'classic'`)
334
+ - `tabindex`: tab index of the code block (default: `0`)
335
+ - `meta`: arbitrary metadata passed to transformers (default: `{}`)
230
336
 
231
337
  ### Component-specific Props
232
338
 
@@ -331,7 +437,7 @@ Ensure your site sets the `color-scheme` CSS property:
331
437
 
332
438
  For broader browser support or more control, add CSS snippets to your site to enable theme switching with media queries or class-based switching. See [Shiki's documentation](https://shiki.matsu.io/guide/dual-themes) for the required CSS snippets.
333
439
 
334
- > **Note**: The `light-dark()` function requires modern browser support. For older browsers, use the manual CSS variables approach.
440
+ > **Note**: The `light-dark()` function requires [modern browser support](https://caniuse.com/wf-light-dark). For older browsers, use the manual CSS variables approach.
335
441
 
336
442
  ### Custom Themes
337
443
 
@@ -349,6 +455,26 @@ import tokyoNight from "../styles/tokyo-night.json";
349
455
  const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight);
350
456
  ```
351
457
 
458
+ When using a fine-grained highlighter via `react-shiki/core`: pass the theme as a dynamic import to `createHighlighterCore` so it stays out of the initial chunk, then reference it by its `name` field:
459
+
460
+ ```tsx
461
+ const highlighter = await createHighlighterCore({
462
+ themes: [import("../styles/tokyo-night.json")],
463
+ langs: [import("@shikijs/langs/tsx")],
464
+ engine: createJavaScriptRegexEngine(),
465
+ });
466
+
467
+ // Component
468
+ <ShikiHighlighter highlighter={highlighter} language="tsx" theme="tokyo-night">
469
+ {code.trim()}
470
+ </ShikiHighlighter>
471
+
472
+ // Hook
473
+ const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight, {
474
+ highlighter,
475
+ });
476
+ ```
477
+
352
478
  ### Custom Languages
353
479
 
354
480
  Custom languages should be passed as a TextMate grammar JavaScript object ([example](https://github.com/shikijs/textmate-grammars-themes/blob/main/packages/tm-grammars/grammars/typescript.json)).
@@ -364,6 +490,28 @@ import mcfunction from "../langs/mcfunction.tmLanguage.json";
364
490
  const highlightedCode = useShikiHighlighter(code, mcfunction, "github-dark");
365
491
  ```
366
492
 
493
+ Passing grammar objects as props works with the full and web bundles, where react-shiki creates the highlighter and loads them for you. To keep custom grammars out of the main bundle, pass them as dynamic imports via `preloadLanguages` and reference them by name — see [Preloading Languages](#preloading-languages).
494
+
495
+ On the core bundle you own the highlighter, so include grammars there as dynamic imports:
496
+
497
+ ```tsx
498
+ const highlighter = await createHighlighterCore({
499
+ themes: [import("@shikijs/themes/github-dark")],
500
+ langs: [import("../langs/mcfunction.tmLanguage.json")],
501
+ engine: createJavaScriptRegexEngine(),
502
+ });
503
+
504
+ // Component
505
+ <ShikiHighlighter highlighter={highlighter} language="mcfunction" theme="github-dark">
506
+ {code.trim()}
507
+ </ShikiHighlighter>
508
+
509
+ // Hook
510
+ const highlightedCode = useShikiHighlighter(code, "mcfunction", "github-dark", {
511
+ highlighter,
512
+ });
513
+ ```
514
+
367
515
  #### Preloading Languages
368
516
 
369
517
  If highlighting is dynamic at runtime, preload custom languages.
@@ -387,8 +535,29 @@ const highlightedCode = useShikiHighlighter(code, "typescript", "github-dark", {
387
535
  });
388
536
  ```
389
537
 
538
+ Custom grammars can also be dynamic imports, keeping them out of the main bundle. Reference them by the grammar's `name` field:
539
+
540
+ ```tsx
541
+ // Module scope: stable identity across renders, fetched on first highlight
542
+ const preloadLanguages = [
543
+ () => import("../langs/mcfunction.tmLanguage.json"),
544
+ () => import("../langs/bosque.tmLanguage.json"),
545
+ ];
546
+
547
+ <ShikiHighlighter
548
+ language="mcfunction"
549
+ theme="github-dark"
550
+ preloadLanguages={preloadLanguages}
551
+ >
552
+ {code.trim()}
553
+ </ShikiHighlighter>
554
+ ```
555
+
556
+ > [!IMPORTANT]
557
+ > Define dynamic imports at module scope, preferring the getter form (`() => import(...)`). A fresh arrow function on every render re-triggers highlighting; bare promises are compared by type, so swapping one at runtime won't be picked up.
558
+
390
559
  > [!NOTE]
391
- > Bundled languages are loaded on demand and do not need to be preloaded.
560
+ > Bundled languages are loaded on demand and do not need to be preloaded. `preloadLanguages` applies to the full and web bundles; with a custom highlighter, load languages in `createHighlighterCore` instead.
392
561
 
393
562
  ### Language Aliases
394
563
 
@@ -442,7 +611,7 @@ Line numbers are CSS-based and can be customized with CSS variables:
442
611
  {code}
443
612
  </ShikiHighlighter>
444
613
 
445
- // Hook (import 'react-shiki/css' for line numbers to work)
614
+ // Hook (line-number styles inject automatically when enabled)
446
615
  const highlightedCode = useShikiHighlighter(code, "javascript", "github-dark", {
447
616
  showLineNumbers: true,
448
617
  startingLineNumber: 0,
@@ -451,14 +620,13 @@ const highlightedCode = useShikiHighlighter(code, "javascript", "github-dark", {
451
620
  ```
452
621
 
453
622
  > [!NOTE]
454
- > When using the hook with line numbers or highlighted lines, import the CSS file or provide your own CSS
455
- > for `.rs-line-number` (line `span`), `.rs-highlighted-line` (line `span`), and `.rs-has-line-numbers` / `.rs-has-highlighted-lines` (container `code` element).
456
- > ```tsx
457
- > import 'react-shiki/css';
458
- > ```
623
+ > No CSS import is needed: the shared highlighting hook injects line-number and highlight styles automatically when those features are enabled, including when used through the component.
624
+ > To customize them, override `.rs-line-number` (line `span`), `.rs-highlighted-line` (line `span`), and `.rs-has-line-numbers` / `.rs-has-highlighted-lines` (container `code` element) in your own CSS.
459
625
 
460
626
  `highlightLineNumbers` uses displayed line numbers, so it works with `startingLineNumber`.
461
627
 
628
+ Component styles are embedded in the JS and installed automatically when the component mounts — no CSS import or bundler configuration needed. Injection is scoped to usage: component styles tree-shake out of hook-only bundles entirely, while line-number/highlight styles are installed only when those options are enabled. The complete compiled stylesheet is also exported as `react-shiki/css` for build-time CSS pipelines or as an externally loaded fallback. Importing it does not disable automatic runtime injection.
629
+
462
630
  Component-internal default classes are namespaced under `rs-*` and written with zero-specificity `:where()` selectors, so they can't be clobbered by CSS resets (e.g. Tailwind preflight) regardless of stylesheet load order, while any rule in your own CSS — even a bare element selector — overrides them.
463
631
 
464
632
  Available CSS variables for customization:
@@ -630,57 +798,6 @@ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
630
798
  });
631
799
  ```
632
800
 
633
- ### Output Formats
634
-
635
- `react-shiki` can return highlighted code in three formats:
636
-
637
- **React Nodes (Default)** - Rendered as React elements, no `dangerouslySetInnerHTML` required
638
- ```tsx
639
- // Hook
640
- const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark");
641
-
642
- // Component
643
- <ShikiHighlighter language="tsx" theme="github-dark">
644
- {code}
645
- </ShikiHighlighter>
646
- ```
647
-
648
- **HTML String** - Rendered via `dangerouslySetInnerHTML`
649
- ```tsx
650
- // Hook (returns HTML string, use dangerouslySetInnerHTML to render)
651
- const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
652
- outputFormat: 'html'
653
- });
654
-
655
- // Component (automatically uses dangerouslySetInnerHTML when outputFormat is 'html')
656
- <ShikiHighlighter language="tsx" theme="github-dark" outputFormat="html">
657
- {code}
658
- </ShikiHighlighter>
659
- ```
660
-
661
- **Shiki Tokens (Experimental)** - Hook-only output for custom renderers
662
- ```tsx
663
- const highlighted = useShikiHighlighter(code, "tsx", "github-dark", {
664
- outputFormat: "tokens",
665
- });
666
-
667
- const rendered = highlighted?.tokens.map((line, i) => (
668
- <div key={i}>
669
- {line.map((token, j) => (
670
- <span key={j} style={{ color: token.color }}>
671
- {token.content}
672
- </span>
673
- ))}
674
- </div>
675
- ));
676
- ```
677
-
678
- The React and HTML formats spend their rendering work in different phases. HTML output skips per-token React element creation and reconciliation, reducing render-phase overhead for large, one-shot highlights, but each update replaces the code block's entire DOM subtree via `innerHTML`. React output pays for element creation and diffing on every update, but commits only incremental DOM mutations, minimizing DOM churn for frequently re-highlighted code such as streaming LLM output.
679
-
680
- HTML output hands the highlighted markup to the DOM via `dangerouslySetInnerHTML`, so only use it with trusted code sources. The default React output is the safe choice for untrusted content.
681
-
682
- Token output is for when you need to own rendering yourself; it is intentionally available on the hook, not the `ShikiHighlighter` component, since react-shiki's markup-based features no longer apply once you control the markup. Note that Shiki's `codeToTokens` does not run transformers or decorations, so markup-producing options, including transformers, decorations, line numbers, and `structure`, have no effect on token output.
683
-
684
801
  ---
685
802
 
686
803
  Made with ❤️ by [Bassim (AVGVSTVS96)](https://github.com/AVGVSTVS96)