Part #1 - embedding runnable / editable code in VitePress
I needed a code sandbox for my side-projects VitePress documentation . VitePress takes markdown files (which I like) and server-side generates the site components which it displays as a SPA via Vue code in the browser. It makes a decent, modern-looking documentation site with little cognitive overhead. I mostly just write markdown and VitePress makes it look good.
Now I needed code embeds for demo code. VitePress lets you add static fenced code blocks. I needed interactive, or at least something that would run in place and had a link to an external editor.
What did you try
I tried SandPack VitePress Plugin but I wasn't up to the task of wiring it all together. It looked straightforward but I just couldn't make it work. Maybe version incompatibility. But I was becoming uncomfortable with future technical debt in the form of reliability and dependency admin.
I looked at a pragmatic solution in the form of Chris Ferdenandi's CodeSandbox web component. This had a lot going for it and would have been my selected solution were it not for the hassle of the VitePress approach. VitePress messes with any actual HTML that it finds embedded in an md file. I fought it through a few rounds but whilst the result kinda worked, it was too clunky and had a visible loading jank. I stress this was all my fault - Chris' code was solid and I would select it again in a less opinionated (not VitePress!) case.
I tried my usual go-to online editor CodePen which I've used for years. It has an embed feature that spits out a string defining an iframe, para, or script to paste into your docs. VitePress handles iframes painlessly so this was looking like a viable option. Ultimately I had problems getting it to display the iframe at a height I dictated without double scroll bars, and I thought the logo was a bit too distracting. The load time seemed slightly slow too.
My current solution is jsfiddle - another old-timer that's been around for ages just doing what it does and doing it well. The jsFiddle embeds take the same route as those for CodePen, with less options. But there's an iframe option, which works for me. I find it loads quickly and their iframe sizing script does what it needs to do and avoids the double-scrollbar issue.
Part #2 - how to make custom containers in VitePress
Containers are those things in VitePress that start and end with three colons. Like Tip, Danger and Warning. So it turns out there is a way to make your own custom container behaviour.
I found this out because as I fumbled my way through the embedded code conundrum one of the possible solutions mentioned above required me to add markup tags to indicate where I wanted to see a code embed and what the associated HTML and JS files would be.
This is actually a good thing because it would keep separation of concerns. I would just write markdown as usual and use the special markers where I wanted the embeds to appear, leaving VitePress do the assembly of the solution as it want along.
The custom container md would looks this:
::: embed <jsFiddle embed key>, <optional iframe height>
:::
Cool - I can cope with that!
How to do that
So there's a VitePress plugin named markdown-it-container. Install that then need to edit the VitePress config file, adding the following markdown section after themeConfig.
It should be fairly plain what's going on apart from the token parts. This is used by the the vitepress file parser. Essentially, having come across a custom container with the configured name, each line it finds is it wrapped up as a token, and the token.info is the actual line content.
So in my case, when the VitePress processor sees a container named embed it fires the markdown-it-container use statement where I grab the jsFiddle embed code and optional height override. These get written out into the jsFiddle iframe embed, et voila. At runtime the iframe contents are pulled in and we have code and live demo. I never have to code the iframe tag by hand.
// add this after themeConfig
markdown: {
config: (md) => {
// the second parameter is html tag name
md.use(markdownItContainer, 'embed', {
render (tokens: any, idx: number) {
if (tokens[idx].type === 'container_embed_open') {
// split by comma or space
const params = tokens[idx].info.split(/[ ,]+/).filter(function(v){return v!==''})
// first param is the ref, second is height
const ref = params[1];
const height = params[2] ? params[2] : '300';
// write into the vitepress page output. This sets up the info that the inline codebox code will use later.
return `<iframe width="100%" height="${height}" src="//jsfiddle.net/VanquishedWombat/${ref}/embedded/result,js/" frameborder="0" loading="lazy" allowtransparency="true" allowfullscreen="true">`
}
else if (tokens[idx].type === 'container_embed_close') {
// we need to put something out here.
return '</iframe>'
}
},
});
}
}
Summary
We've seen the results of my journey to embed live JS into my docs for that side project. I'll be launching it soon so you'll get to experience the results then!
We've also got a usable approach to customer containers written in VitePress markdown syntax that could be a gateway to making a smooth workflow for linking anything into your VitePress docs.
Thanks for reading.
VW Sep 2025