VitePress-style code group tabs and container directives for mdsvex / SvelteKit.
Turn this markdown:
::: code-group
```typescript [TypeScript]
const x: number = 1;
```
```python [Python]
x: int = 1
```
:::
Into tabbed code blocks — click TypeScript or Python to switch.
npm install svelte-code-tabs
svelte.config.jsimport { containerPreprocess } from 'svelte-code-tabs';
import { mdsvex } from 'mdsvex';
const config = {
extensions: ['.svelte', '.md'],
preprocess: [
containerPreprocess(), // must come BEFORE mdsvex
mdsvex({ extensions: ['.md'] })
]
};
// In your root layout or app.css
import 'svelte-code-tabs/style.css';
Or copy the CSS variables into your own stylesheet for customization.
<script>
import { onMount } from 'svelte';
import { afterNavigate } from '$app/navigation';
import { browser } from '$app/environment';
import { upgradeCodeGroups } from 'svelte-code-tabs';
let contentEl = $state(null);
function init() {
if (contentEl) upgradeCodeGroups(contentEl);
}
onMount(init);
if (browser) afterNavigate(init);
</script>
<main bind:this={contentEl}>
<slot />
</main>
::: code-group
```typescript [TypeScript]
const x = 1;
```
```python [Python]
x = 1
```
:::
The [Label] after the language sets the tab name. Without it, the language name is used.
::: tip Optional Title
Content here.
:::
::: warning
This is a warning.
:::
::: info
Informational note.
:::
Override these in your stylesheet:
:root {
--cg-border: #e2e2e3;
--cg-bg: #fff;
--cg-bg-soft: #f9f9f9;
--cg-color-active: #111;
--cg-color-hover: #333;
--cg-color-muted: #888;
--cg-font-mono: ui-monospace, Menlo, Monaco, Consolas, monospace;
}
containerPreprocess({
labelClass: 'cg-label', // CSS class for hidden label spans
calloutClass: 'callout' // CSS class prefix for callout divs
})
upgradeCodeGroups(container, {
wrapperClass: 'cg-wrapper',
tabBarClass: 'cg-tabs',
tabClass: 'cg-tab',
activeClass: 'active',
labelClass: 'cg-label',
languageLabels: {
typescript: 'TypeScript',
python: 'Python',
// ... add your own
}
});
Preprocessor (build time): Runs before mdsvex. Strips ::: directive lines, extracts [Label] from code fences into hidden <span> elements, converts callout directives to styled <div>s.
mdsvex (build time): Processes the markdown normally — code fences get syntax highlighting, curly braces get escaped, everything works.
Client-side (runtime): upgradeCodeGroups() finds consecutive <pre> elements, reads their labels from the hidden spans, and wraps them in a tabbed interface.
mdsvex strips the [Label] portion from code fence info strings before passing to remark or the highlighter. A Svelte preprocessor runs on the raw markdown string before any parsing, preserving full control.
Wrapping code fences in <Component> tags causes mdsvex to treat the code as raw HTML rather than markdown. Curly braces in code examples ({ name: 'x' }) get parsed as Svelte template expressions and break compilation.
MIT