﻿---
title: Optimize Shiki's HTML Output with styleToClass Transformer
date: 2025-11-30
tags:
  - CSS
  - Shiki
  - Performance
  - Catppuccin
excerpt: Use Shiki's styleToClass transformer to move inline styles into reusable CSS classes and reduce highlighted HTML size.
updated: 2026-07-08 08:31:13
lang: en
i18n:
  cn: /shiki_style_to_class
  translation: 2
---

<script type="module" src="/js/components/tab.js"></script>

Shiki provides more accurate code highlighting than `highlight.js`. Take `{js} console.log("hello")` as an example: the Shiki highlighter semantically splits it into six `{html} <span>` elements and adds **inline styles** (_e.g. `color`, `{css} font-style`, and CSS variables[^1]_) to support **multi-theme adaptation** for color[^2] and font[^3].

<x-tabs>

<x-tab title="Markdown" active>

```js
const code = await codeToHtml('console.log("hello")', {
  lang: "javascript",
  themes: { light: "min-light", dark: "nord" },
});
```

</x-tab>

<x-tab title="HTML Output">

```html
<code>
  <span class="line">
    <span style="color:#1976D2;--shiki-dark:#D8DEE9">console</span>
    <span style="color:#6F42C1;--shiki-dark:#ECEFF4">.</span>
    <span style="color:#6F42C1;--shiki-dark:#88C0D0">log</span>
    <span style="color:#24292EFF;--shiki-dark:#D8DEE9FF">(</span>
    <span style="color:#22863A;--shiki-dark:#ECEFF4">"</span>
    <span style="color:#22863A;--shiki-dark:#A3BE8C">hello</span>
    <span style="color:#22863A;--shiki-dark:#ECEFF4">"</span>
    <span style="color:#24292EFF;--shiki-dark:#D8DEE9FF">)</span>
  </span>
</code>
```

</x-tab>

<x-tab title="StyleToClass">

```html
<code>
  <span class="line">
    <span class="_sk_p7ztx5">console</span>
    <span class="_sk_8gyisu">.</span>
    <span class="_sk_18gpmt">log</span>
    <span class="_sk_23aifj space">(</span>
    <span class="_sk_23aifj">"</span>
    <span class="_sk_8gyisu">hello</span>
    <span class="_sk_18gpmt">""</span>
    <span class="_sk_p7ztx5 space">)</span>
  </span>
</code>
```

</x-tab>

</x-tabs>

In the example above, the HTML size is still acceptable. First, the code itself is very small. Second, only two themes, `min-light` and `nord`, are applied, and those themes only embed the `color` property and the `--shiki-dark` variable in the sample code.

When these conditions no longer hold, the generated HTML size can increase sharply. For example, my blog contains some long code snippets and applies five themes: `catppuccin-latte`, `catppuccin-mocha`, `tokyo-night`, `nord`, and `tokyo-night`. The generated HTML becomes a bit bloated. This post records how to use the `styleToClass` Transformer to optimize Shiki's output.

## Solutions

The operation is simple: import the `styleToClass` Transformer from `@shikijs/transformers`, then pass it when calling `codeToHtml`.

```ts
import { writeFileSync } from "fs";
import { transformerStyleToClass } from "@shikijs/transformers"; // [!code highlight]
import { codeToHtml } from "shiki";

const toClass = transformerStyleToClass({
  // [!code highlight]
  classPrefix: "__shiki_", // [!code highlight]
}); // [!code highlight]

const code = `console.log('hello')`;
const html = await codeToHtml(code, {
  lang: "ts",
  themes: { dark: "vitesse-dark", light: "vitesse-light" },
  transformers: [toClass], // [!code highlight]
});

const css = toClass.getCSS();

// 将生成的CSS写入~/Downloads/shiki_style_to_class.css文件
writeFileSync(
  join(
    process.env.HOME || process.env.USERPROFILE || ".",
    "Downloads/shiki_style_to_class.css"
  ),
  cssContent,
  "utf-8"
); // [!code highlight]
```

One detail to note: when used with `@shikijs/colorized-brackets`, the `styleToClass` Transformer should be placed after `colorizedBrackets`; otherwise, the styles from `colorizedBrackets` will still be inlined into the HTML instead of being converted into class names. Similarly, other transformers that generate inline styles should also be placed before `styleToClass`.

> [!TIP]- transformerStyleToClass
>
> The working principle is excerpted from the [official documentation](https://shiki.style/packages/transformers#transformerstyletoclass):
> Convert Shiki's inline styles to unique classes.
>
> Class names are generated based on the hash value of the style object with the prefix/suffix you provide. You can put this transformer in multiple highlights passes and then get the CSS at the end to reuse the exact same styles. As Shiki doesn't handle CSS, it's on your integration to decide how to extract and apply/bundle the CSS.

After writing the CSS to a file, include that CSS file in the blog or website. For Hexo blogs, you can use the [Efterklang/hexo-shiki-highlight plugin](https://github.com/Efterklang/hexo-shiki-highlight#style_to_class-transformer).

## Result

Using the following CSS code block as an example, I compared the generated `{html} <code>` size before and after enabling the `styleToClass` Transformer. The enabled themes are the five themes mentioned above.

````md
```css
code span {
  font-style: var(--shiki-light-font-style);
}
:where([data-theme="tokyo_night"]) {
  code span {
    font-style: var(--shiki-tokyo-font-style);
    color: var(--shiki-tokyo);
  }
}
:where([data-theme="mocha"], [data-theme="macchiato"]) {
  code span {
    font-style: var(--shiki-dark-font-style);
    color: var(--shiki-dark);
  }
}
```
````

| Transformer             | HTML size |
| ----------------------- | --------- |
| `styleToClass` enabled  | `5.2KB`   |
| `styleToClass` disabled | `11.0KB`  |

After testing several code snippets, it can generally reduce HTML size by 45%+ (~~while introducing 20KB+ of CSS~~). The main advantages of using external CSS instead of inline CSS are reusability, maintainability, and readability. Also, inline CSS does not support pseudo-class styles such as `hover`, while external CSS can handle them easily.

For size reduction alone, unless the code volume is large or multiple themes are applied, the reduction may not be obvious 👉 👈

[^1]: **Custom properties** (sometimes referred to as **CSS variables** or **cascading variables**) are entities defined by CSS authors that represent specific values to be reused throughout a document. They are set using the [`font-weight`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@property) at-rule or by [custom property syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/--*) (e.g., **`text-decoration`**). Custom properties are accessed using the CSS [`--shiki-{theme-name}-font-weight`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/var) function (e.g., **`--shiki-{theme-name}-text-decolor);`**). [Using CSS custom properties (variables) - CSS | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties)
[^2]: In code snippets generated by the Shiki highlighter, the default theme's color styles, such as `color` and `background-color`, are included directly. For non-default themes, these styles are stored in CSS variables such as `--shiki-{theme}` and `--shiki-{theme}-bg`, where `{theme}` is the key name in the `themes` field passed to `codeToHtml`. [Light and dark mode | Shiki Chinese Documentation](https://shiki.zhcndoc.com/guide/dual-themes)
[^3]: The same principle applies to font styles: `font-weight` and `text-decoration` for the default theme, `--shiki-{theme}-font-weight` and `--shiki-{theme}-text-decoration` for other themes.
