Skip to content

Commit 1eaf27c

Browse files
authored
fix: rename css layer from base to react-shiki (#169)
tailwind v3 reserves @layer base in its postcss plugin, so our css failed to compile in tailwind v3 apps. a dedicated layer name passes through untouched and keeps the same cascade behavior.
1 parent 463da64 commit 1eaf27c

5 files changed

Lines changed: 38 additions & 41 deletions

File tree

.changeset/rename-css-layer.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"react-shiki": patch
3+
---
4+
5+
Fix: rename the CSS cascade layer from `base` to `react-shiki`. Tailwind v3's PostCSS plugin reserves `@layer base` as a directive, so react-shiki's CSS failed to compile in Tailwind v3 apps. A dedicated layer name passes through untouched.
6+
7+
If you rely on layer ordering (e.g. overriding react-shiki defaults with Tailwind v4 utilities), declare the order in your stylesheet: `@layer react-shiki, theme, base, components, utilities;`

package/README.md

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# 🎨 react-shiki
22

3-
43
A performant client-side syntax highlighting component and hook for React, built with [Shiki](https://shiki.matsu.io/).
54

65
[See the demo page with highlighted code blocks showcasing several Shiki themes!](https://react-shiki.vercel.app/)
@@ -37,14 +36,14 @@ A performant client-side syntax highlighting component and hook for React, built
3736
- [Handling Inline Code](#handling-inline-code)
3837
- [Performance](#performance)
3938
- [Throttling Real-time Highlighting](#throttling-real-time-highlighting)
40-
- [Output Format Optimization](#output-format-optimization)
39+
- [Output Formats](#output-formats)
4140
<!--toc:end-->
4241

4342
## Features
4443

4544
- 🖼️ Provides both a `ShikiHighlighter` component and a `useShikiHighlighter` hook for more flexibility
46-
- 🔐 Flexible output: Choose between React elements (no `dangerouslySetInnerHTML`) or HTML strings for better performance
47-
- 📦 Multiple bundle options: Full bundle (~1.2MB gz), web bundle (~695KB gz), or minimal core bundle for fine-grained bundle control
45+
- 🔐 Flexible output: Choose between React elements (no `dangerouslySetInnerHTML`) or HTML strings
46+
- 📦 Multiple bundle options: Full bundle (~1.2MB gz), web bundle (~707KB gz), or minimal core bundle for fine-grained bundle control
4847
- 🖌️ Full support for custom TextMate themes and languages
4948
- 🧬 Automatic highlighting of embedded languages (e.g. TypeScript fenced inside Markdown) via Shiki's `guessEmbeddedLanguages`
5049
- 🔧 Supports passing custom Shiki transformers to the highlighter, in addition to all other options supported by `codeToHast`
@@ -203,17 +202,15 @@ createJavaScriptRegexEngine({ forgiving: true });
203202

204203
See [Shiki - RegExp Engines](https://shiki.style/guide/regex-engines) for more info.
205204

206-
207205
## Configuration
208206

209207
### Common Configuration Options
210208

211-
212209
| Option | Type | Default | Description |
213210
| ------------------- | ------------------ | --------------- | ----------------------------------------------------------------------------- |
214211
| `code` | `string` | - | Code to highlight |
215-
| `language` | `string \| object` | - | Language to highlight, built-in or custom textmate grammer object |
216-
| `theme` | `string \| object` | `'github-dark'` | Single or multi-theme configuration, built-in or custom textmate theme object |
212+
| `language` | `string \| object` | - | Language to highlight, built-in or custom TextMate grammar object |
213+
| `theme` | `string \| object` | `'github-dark'` | Single or multi-theme configuration, built-in or custom TextMate theme object |
217214
| `delay` | `number` | `0` | Delay between highlights (in milliseconds) |
218215
| `customLanguages` | `array` | `[]` | **Deprecated**: use `preloadLanguages` instead. |
219216
| `preloadLanguages` | `array` | `[]` | Preload bundled language IDs and custom language grammars |
@@ -227,7 +224,7 @@ See [Shiki - RegExp Engines](https://shiki.style/guide/regex-engines) for more i
227224
| `outputFormat` | `string` | `'react'` | Output format: 'react' for React nodes, 'html' for HTML string |
228225
| `tabindex` | `number` | `0` | Tab index for the code block |
229226
| `decorations` | `array` | `[]` | Custom decorations to wrap the highlighted tokens with |
230-
| `structure` | `string` | `classic` | The structure of the generated HAST and HTML - `classic` or `inline` |
227+
| `structure` | `string` | `'classic'` | The structure of the generated HAST and HTML - `classic` or `inline` |
231228
| [`codeToHastOptions`](https://github.com/shikijs/shiki/blob/main/packages/types/src/options.ts#L121) | - | - | All other options supported by Shiki's `codeToHast` |
232229

233230
### Component-specific Props
@@ -246,7 +243,7 @@ The `ShikiHighlighter` component offers minimal built-in styling and customizati
246243

247244
### Embedded Language Highlighting
248245

249-
react-shiki uses Shiki's [`guessEmbeddedLanguages`](https://shiki.style) to automatically detect, load, and highlight languages nested inside other languages — for example, a TypeScript fenced code block inside a Markdown document will be highlighted as TypeScript without any extra configuration. Embedded languages are dynamically loaded on demand, as long as they exist in your bundle.
246+
react-shiki uses Shiki's [`guessEmbeddedLanguages`](https://github.com/shikijs/shiki/blob/main/packages/core/src/utils/strings.ts) to automatically detect, load, and highlight languages nested inside other languages. For example, a TypeScript fenced code block inside a Markdown document will be highlighted as TypeScript without any extra configuration. Embedded languages are dynamically loaded on demand, as long as they exist in your bundle.
250247

251248
### Multi-theme Support
252249

@@ -337,7 +334,7 @@ For broader browser support or more control, add CSS snippets to your site to en
337334
338335
### Custom Themes
339336

340-
Custom themes can be passed as a TextMate theme in JavaScript object. For example, [it should look like this](https://github.com/antfu/textmate-grammars-themes/blob/main/packages/tm-themes/themes/dark-plus.json).
337+
Custom themes can be passed as a TextMate theme JavaScript object ([example](https://github.com/antfu/textmate-grammars-themes/blob/main/packages/tm-themes/themes/dark-plus.json)).
341338

342339
```tsx
343340
import tokyoNight from "../styles/tokyo-night.json";
@@ -353,7 +350,7 @@ const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight);
353350

354351
### Custom Languages
355352

356-
Custom languages should be passed as a TextMate grammar in JavaScript object. For example, [it should look like this](https://github.com/shikijs/textmate-grammars-themes/blob/main/packages/tm-grammars/grammars/typescript.json)
353+
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)).
357354
```tsx
358355
import mcfunction from "../langs/mcfunction.tmLanguage.json";
359356

@@ -390,7 +387,7 @@ const highlightedCode = useShikiHighlighter(code, "typescript", "github-dark", {
390387
```
391388

392389
> [!NOTE]
393-
> Bundled language are dynamically loaded as needed, and do not need to be preloaded as long as they exist in your bundle.
390+
> Bundled languages are loaded on demand and do not need to be preloaded.
394391
395392
### Language Aliases
396393

@@ -430,29 +427,19 @@ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
430427

431428
### Line Numbers
432429

433-
Display line numbers alongside your code, these are CSS-based
434-
and can be customized with CSS variables:
430+
Line numbers are CSS-based and can be customized with CSS variables:
435431

436432
```tsx
437433
// Component
438434
<ShikiHighlighter
439435
language="javascript"
440436
theme="github-dark"
441-
showLineNumbers,
437+
showLineNumbers
442438
startingLineNumber={0} // default is 1
443439
>
444440
{code}
445441
</ShikiHighlighter>
446442

447-
<ShikiHighlighter
448-
language="python"
449-
theme="github-dark"
450-
showLineNumbers
451-
startingLineNumber={0}
452-
>
453-
{code}
454-
</ShikiHighlighter>
455-
456443
// Hook (import 'react-shiki/css' for line numbers to work)
457444
const highlightedCode = useShikiHighlighter(code, "javascript", "github-dark", {
458445
showLineNumbers: true,
@@ -462,15 +449,16 @@ const highlightedCode = useShikiHighlighter(code, "javascript", "github-dark", {
462449

463450
> [!NOTE]
464451
> When using the hook with line numbers, import the CSS file or provide your own CSS
465-
> for `.rs-line-number` (line `span`) and `.rs-has-line-numbers` (container `code` element). <!-- TODO: The CSS vars/classes/selectors are poorly documented -->
452+
> for `.rs-line-number` (line `span`) and `.rs-has-line-numbers` (container `code` element).
466453
> ```tsx
467454
> import 'react-shiki/css';
468455
> ```
469456
470-
> [!WARNING]
471-
> Legacy `.line-numbers` / `.has-line-numbers` selectors and unprefixed line-number CSS variables are removed as of 0.10.1.
457+
Component-internal default classes are namespaced under `rs-*` and shipped inside the `@layer react-shiki` cascade layer, so unlayered app CSS and later layers take precedence over them. To control precedence explicitly, declare the layer order at the top of your stylesheet:
472458
473-
Component-internal default classes are namespaced under `rs-*` and shipped inside `@layer base` so app-level utilities can override them more predictably.
459+
```css
460+
@layer react-shiki, theme, base, components, utilities;
461+
```
474462
475463
Available CSS variables for customization:
476464
```css
@@ -573,9 +561,9 @@ const CodeHighlight = ({ className, children, node, ...props }) => {
573561
**Method 2: Using the `rehypeInlineCodeProperty` plugin:**
574562

575563
`react-shiki` also exports `rehypeInlineCodeProperty`, a rehype plugin that
576-
provides the same API as `react-markdown` prior to `9.0.0`. It reintroduces the
577-
`inline` prop which works by checking if `<code>` is nested within a `<pre>` tag,
578-
if not, it's considered inline code and the `inline` prop is set to `true`.
564+
adds an `inline` prop to `react-markdown` code components. It works by checking
565+
if `<code>` is nested within a `<pre>` tag; if not, it's considered inline code
566+
and the `inline` prop is set to `true`.
579567

580568
It's passed as a `rehypePlugin` to `react-markdown`:
581569

@@ -623,7 +611,7 @@ const CodeHighlight = ({
623611

624612
### Throttling Real-time Highlighting
625613

626-
For improved performance when highlighting frequently changing code:
614+
To reduce highlighting work for frequently changing code:
627615

628616
```tsx
629617
// With the component
@@ -637,11 +625,11 @@ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
637625
});
638626
```
639627

640-
### Output Format Optimization
628+
### Output Formats
641629

642-
`react-shiki` provides two output formats to balance safety and performance:
630+
`react-shiki` can return highlighted code in two formats:
643631

644-
**React Nodes (Default)** - Safer, no `dangerouslySetInnerHTML` required
632+
**React Nodes (Default)** - Rendered as React elements, no `dangerouslySetInnerHTML` required
645633
```tsx
646634
// Hook
647635
const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark");
@@ -652,7 +640,7 @@ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark");
652640
</ShikiHighlighter>
653641
```
654642

655-
**HTML String** - 15-45% faster performance
643+
**HTML String** - Rendered via `dangerouslySetInnerHTML`
656644
```tsx
657645
// Hook (returns HTML string, use dangerouslySetInnerHTML to render)
658646
const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
@@ -665,7 +653,9 @@ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
665653
</ShikiHighlighter>
666654
```
667655

668-
Choose HTML output when performance is critical and you trust the code source. Use the default React output when handling untrusted content or when security is the primary concern.
656+
The two 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.
657+
658+
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.
669659

670660
---
671661

package/src/lib/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ interface ReactShikiOptions {
8686
/**
8787
* Output format for the highlighted code.
8888
* - 'react': Returns React nodes (default, safer)
89-
* - 'html': Returns HTML string (~15-45% faster, requires dangerouslySetInnerHTML)
89+
* - 'html': Returns HTML string (rendered via dangerouslySetInnerHTML)
9090
* @default 'react'
9191
*/
9292
outputFormat?: 'react' | 'html';

package/src/styles/component.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@layer base {
1+
@layer react-shiki {
22
.rs-root {
33
position: relative;
44

package/src/styles/features.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@layer base {
1+
@layer react-shiki {
22
.rs-has-line-numbers {
33
counter-reset: line-number calc(var(--line-start, 1) - 1);
44

0 commit comments

Comments
 (0)