Use Shiki to Style Code Blocks in HTML Emails
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:
Next, we need to install shiki-es, a standalone build of Shiki fully compatible with all ESM environments:
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:
Shiki Code
Now it's time to manually call Shiki to render our code as an HTML table. We, therefore, use shiki-es, a standalone build of Shiki that is fully compatible with all ESM environments.
Three steps are necessary to generate the HTML code:
- Use
getHighlighter
to get an instance of the Shiki highlighter. - Call
codeToThemeTokens
with the given code and language to get the tokens that should be rendered. - Use
renderToHtml
to generate the HTML which should be rendered in the DOM. Itselements
object can be used to modify the DOM structure of the code block. Here we add our<table>
,<td>
and<tr>
tags which are necessary for the HTML table.
Let's take a look at the code:
This code will result in this DOM structure:
CSS Styles
As mentioned, I use Tailwind in my project which heavily uses rgb
colors. Unfortunately, rgb()
works partially in email clients but alpha values and whitespace syntax are not supported.
I use the postcss-preset-env PostCSS plugin to convert modern CSS into something most browsers can understand:
Final Result
For example, this is how a code block in a newsletter email from Weekly Vue News looks like in a Gmail web client:
Conclusion
Styling HTML emails is a real pain but having good-looking code blocks in my newsletter emails was worth the effort. It's very sad that we still need to use HTML tables in HTML emails in the year 2023...
Thankfully, Nuxt Content is very customizable and allows developers to build custom solutions.
If you liked this article, follow me on Twitter to get notified about new blog posts and more content from me.
Alternatively (or additionally), you can subscribe to my weekly Vue newsletter.
How I Replaced Revue With a Custom-Built Newsletter Service Using Nuxt 3, Supabase, Serverless, and Amazon SES
Twitter will shut down Revue on January 18, 2023, which I previously used as a newsletter provider for Weekly Vue News.
Ref vs. Reactive: What to Choose Using Vue 3 Composition API?
I love Vue 3's Composition API, but it provides two approaches to adding a reactive state to Vue components: ref and reactive. It can be cumbersome to use .value everywhere when using refs but you can also easily lose reactivity when destructuring reactive objects created with reactive.