Javascript is required
ยท
4 min read

Use Shiki to Style Code Blocks in HTML Emails

Use Shiki to Style Code Blocks in HTML Emails Image

I recently developed a custom newsletter service using Nuxt 3. One of the main reasons why I developed it on my own was that I wanted to use good-looking code blocks in my emails.

In this article, I'll explain how I use Shiki to generate nicely styled code blocks in my newsletter emails.

Nuxt 3 & Nuxt Content

Info

If you don't use Nuxt and are only interested in the necessary Shiki customization, you can skip the next sections, and head over to "Shiki Code" section.

On my newsletter website I use Nuxt 3 with the Nuxt Content and Nuxt Tailwind modules.

Nuxt Content uses Shiki that colors tokens with VSCode themes.

By default, it uses code, pre, span and div tags with CSS Flexbox to render the code block.

Unfortunately, flex-direction:column is badly supported in email clients.

Instead, we need to write the code block in an HTML table which is supported in all email clients.

So we need to eject from the default styling and create a custom code component.

Custom Prose Component

Nuxt Content uses Prose components to render markdown files in the DOM.

To overwrite a prose component, we can create a component with the same name in our project components/content/ directory.

In our case, we want to create a custom ProseCode component:

components/content/ProseCode.vue
1<script setup lang="ts">
2const props = withDefaults(
3  defineProps<{ code?: string; language?: string | null; filename?: string | null; highlights?: Array<number> }>(),
4  { code: '', language: null, filename: null, highlights: [] }
5)
6</script>
7
8<template>
9  <div></slot></div>
10</template>

Next, we need to install shiki-es, a standalone build of Shiki fully compatible with all ESM environments:

bash
1# npm
2npm i shiki-es
3
4# yarn
5yarn add shiki-es

We can eject from the default styling by removing the <slot/> tag and adding an html reactive variable that will contain the highlighted which is rendered via the v-html directive:

components/content/ProseCode.vue
1<script setup lang="ts">
2const props = withDefaults(
3  defineProps<{ code?: string; language?: string | null; filename?: string | null; highlights?: Array<number> }>(),
4  { code: '', language: null, filename: null, highlights: [] }
5)
6
7const html = ref('')
8</script>
9
10<template>
11  <div class="code" v-html="html"></div>
12</template>

Shiki Code

Now it's time to manually call Shiki to ren