Guides
Tabs & code
Grouped tabs, synced package managers, copy buttons, and code in every language.
On this page
Documentation lives and dies by its code samples. ogygia gives you tabbed panels for showing the same thing three ways, build-time highlighting for a dozen languages, and a copy button on every block. This page shows all of it.
Tabs
A <TabGroup> wraps two or more <Tab> panels. Each Tab has a label, and the content between the tags is ordinary Markdown — including fenced code, as long as you leave blank lines around the fences.
npm install ogygiapnpm add ogygiayarn add ogygiabun add ogygiaAuthor it like this:
<TabGroup group="install">
<Tab label="npm">
```bash
npm install ogygia
```
</Tab>
<Tab label="pnpm">
```bash
pnpm add ogygia
```
</Tab>
</TabGroup>Synced tab groups
Give two <TabGroup> the same group and they move together. Pick pnpm once and every package-manager block on the page follows — the choice persists across reloads, so a returning reader never re-picks their tool.
npm run devpnpm devyarn devbun devBoth <TabGroup> on this page share group="install". Try switching one — the other switches with it.
Markdown-native tabs
You rarely need the raw components. Two ::: blocks cover most cases, and the tab components are injected for you — no <script> import.
::: code-group puts the label in each fence, VitePress-style:
npm create ogygia@latestpnpm create ogygiabun create ogygia::: tabs takes any Markdown, one tab per == Label:
Install with Homebrew: brew install node.
Use your distro’s package manager, or nvm.
Grab the installer from nodejs.org, or use winget.
Copy buttons
Every fenced block gets a copy button in its top corner. It copies the raw source, not the highlighted HTML, so what lands on the clipboard is exactly what you paste into a terminal or editor. Nothing to configure; it is on by default.
Code in every language
Highlighting is powered by Shiki at build time, so any language VS Code knows, ogygia renders. Here is a tour.
Shell
pnpm add ogygia
pnpm dev --host --port 4321TypeScript
import { content, markdown } from 'ogygia/content';
import { z } from 'zod';
export const guides = content({
source: markdown('guides/**/+doc.svx'),
schema: z.object({
title: z.string(),
summary: z.string().optional()
})
});
const page = await guides.get('guides/tabs-and-code');
console.log(page?.data.title);Svelte
<script lang="ts">
import Counter from '$lib/playground/demos/Counter.svelte' with { wake: 'visible' };
let { title } = $props();
let open = $state(false);
</script>
<h2>{title}</h2>
<button onclick={() => (open = !open)}>Toggle</button>
{#if open}
<Counter />
{/if}JSON
{
"name": "my-docs",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build"
},
"dependencies": {
"ogygia": "^0.5.0"
}
}CSS
:root {
--og-accent: #0d9488;
--og-bg: #ffffff;
--og-text: #1c1c21;
}
.doc-demo {
padding: 1.5rem;
border: 1px solid var(--og-line);
border-radius: 12px;
}HTML
<article class="doc">
<h1>Tabs & code</h1>
<p>Static HTML, rendered on the server.</p>
<div data-island="Counter"><!-- hydrates on visible --></div>
</article>A longer sample
Real routes are longer than a snippet. Highlighting scales fine — here is a full catch-all page component:
<script lang="ts">
import { Doc, SideNav, OnThisPage } from 'ogygia/content';
import { page } from '$app/state';
let { data } = $props();
const { doc, nav } = $derived(data);
const crumbs = $derived(
page.url.pathname.split('/').filter(Boolean)
);
</script>
<div class="layout">
<aside>
<SideNav tree={nav} current={page.url.pathname} />
</aside>
<article>
<nav class="breadcrumbs" aria-label="Breadcrumb">
{#each crumbs as crumb, i}
<span>{crumb}</span>
{#if i < crumbs.length - 1}<span aria-hidden="true">/</span>{/if}
{/each}
</nav>
<h1>{doc.title}</h1>
{#if doc.summary}<p class="summary">{doc.summary}</p>{/if}
<doc.Component />
</article>
<OnThisPage headings={doc.headings} />
</div>Tip. Keep samples runnable. A reader who copies a block should get something that works, not a fragment that needs three imports you did not show.
For the full stress test with tabs, tables, and live demos all at once, see the kitchen sink.