Inline Code Highlighting With Markdown-It & Shiki

I built markdown-it-inline-code on Nov 20–21, 2025 to highlight inline code. This post covers a preview, usage, and the development story.
This post is outdated

The project has switched to Efterklang/hexo-renderer-markdown-exit + markdown-exit-shiki

First, a preview of the result

- **Plain**: `printf("Hello, World")`- **Python**: `{python} print("Hello, World")`- **JavaScript**: `{javascript} console.log("Hello, World")`- **HTML**: `{html} <h1>Hello, World!</h1>`- **Rust** `{rust} fn main() { println!("Hello, World!"); }`- **Shell**: `{shell} echo "Hello, World!"`
  • Plain: printf("Hello, World")
  • Python: print("Hello, World")
  • JavaScript: console.log("Hello, World")
  • HTML: <h1>Hello, World!</h1>
  • Rust fn main() { println!("Hello, World!"); }
  • Shell: echo "Hello, World!"

Tips: If you use Obsidian as your editor, install obsidian-shiki-plugin and enable the inline-highlight feature

Usage

Install a markdown-it renderer

Take Hexo as an example. Its default renderer is marked[1], which converts Markdown into HTML. For example:

  • **Bold** renders as <strong>Bold<strong/>
  • *Italic* renders as <em>Italic<em/>

This plugin is built on the markdown-it renderer, so you need to switch to markdown-it first. I use the hexo-renderer-markdown-it-plus plugin[2]. Run these commands in the root of your Hexo blog:

bun uninstall hexo-renderer-marked --savebun i hexo-renderer-markdown-it-plus --savebun i markdown-it-inline-code@0.2.0

Update the Hexo config:

_config.yml
markdown_it_plus:  highlight: false  html: true  xhtmlOut: true  breaks: true  langPrefix: null  linkify: true  typographer: null  quotes: “”‘’  pre_class: highlight  plugins:    - plugin:      name: markdown-it-inline-code      enable: true

Test the output, add custom styles

Run hexo clean && hexo s. Taking *Italic* as an example, the expected rendered output is:

<code>  <span    style="      color: #d20f39;      --shiki-light-font-style: italic;      --shiki-dark: #f38ba8;      --shiki-dark-font-style: italic;      --shiki-tokyo: #c0caf5;      --shiki-tokyo-font-style: italic;    "  >    Italic  </span></code>

You can refer to my earlier post to make it work with theme switching. Here is a CSS demo that switches via attribute selectors:

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);  }}

Development Story

At first I skimmed markdown-it’s plugin docs and figured it should be easy, so I started coding. The plan was to finish before bed, but I ended up grinding until 4 a.m. and gave up. Partly because I’m no good at TS/JS, partly because coding before sleep is a bad idea.

The next day I cleaned up the code and vibe-coded a beta version. The code quality could be better, but

Mr. Luo
Mr. Luo

Using Shiki Synchronously

Initially I used Shiki’s codeToHtml API, which is async and returns a Promise. Since markdown-it’s render.rules must be synchronous, I followed Synchronous Usage | Shiki docs and used createJavaScriptRegexEngine({lang, themes, engine}) to create a highlighter instance synchronously

index.ts
import { createHighlighterCoreSync } from 'shiki/core'import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'// allLangs: list of languages supported by shikijs// see all-langs.ts in Efterklang/markdown-it-inline-code/srcimport { allLangs } from './all-langs'// themeimport latte from '@shikijs/themes/catppuccin-latte'import mocha from '@shikijs/themes/catppuccin-mocha'import tokyo_night from '@shikijs/themes/tokyo-night'const themes = [latte, mocha, tokyo_night]const SHIKI_KEY = Symbol.for('mdit-inline-code-shiki')// global shiki singleton instancefunction getShiki() {    const g = globalThis as any    if (g[SHIKI_KEY]) return g[SHIKI_KEY]    // Handle potential default export wrapping when bundled/externalized // [!code warning]    const themeList = themes.map(t => (t as any).default || t)    const shiki = createHighlighterCoreSync({        langs: allLangs,        themes: themeList,        engine: createJavaScriptRegexEngine()    })    g[SHIKI_KEY] = shiki    return shiki}

Custom Render Rules

You can override the renderer.rules.code_inline method on the MarkdownIt instance to customize inline code rendering. The idea here:

  1. Match the code content with a regex and split out the language and the code
  2. Call shiki.codeToHtml to highlight the code synchronously
function inlineCodeHighlightPlugin( md: MarkdownIt, _options: null ) {    // get the shiki singleton instance    const shiki = getShiki()    const defaultRender =        md.renderer.rules.code_inline ||        function (tokens, idx, _options, _env, self) {            const token = tokens[idx]            if (!token) return ''            return (                '<code' +                self.renderAttrs(token) +                '>' +                md.utils.escapeHtml(token.content) +                '</code>'            )        }    const themeMap = {        light: 'catppuccin-latte',        dark: 'catppuccin-mocha',        tokyo: 'tokyo-night'    }    md.renderer.rules.code_inline = function (tokens, idx, options, env, self) {        const token = tokens[idx]        if (!token) return ''        const content = token.content.trim()        // capture lang and code from `{lang} code`        const match = content.match(/^\{(\w+)\}\s+(.+)$/)        if (match === null) {            return defaultRender(tokens, idx, options, env, self)        }        try {            const highlighted = shiki.codeToHtml(match[2], {                lang: match[1],                themes: themeMap,                structure: 'inline'            })            return '<code' + self.renderAttrs(token) + '>' + highlighted + '</code>'        } catch (e) {            console.error('Highlighting failed', e)            return defaultRender(tokens, idx, options, env, self)        }    }}export default inlineCodeHighlightPlugin

  1. A markdown parser and compiler. Built for speed. markedjs/marked: A markdown parser and compiler. Built for speed. ↩︎

  2. Markdown-it is a markdown parser, done right. A faster and CommonMark compliant alternative for Hexo. CHENXCHEN/hexo-renderer-markdown-it-plus. The plugin hasn’t been maintained for a while, but it’s simple and there isn’t much to develop anyway. If needed later, I may fork it or look for an alternative ↩︎