Code and diagrams
Fenced code blocks get syntax highlighting, a filename header and a copy button. Mermaid fences render real diagrams that follow the reader's colour mode.
Code blocks
Write a fenced code block. Do not use a component. Shiki highlights a fence,
and the fence also gets the filename header and copy button. These come from
src/lib/rehype-code-chrome.ts,
which adds them.
```ts title="src/client.ts"
export const client = createClient({ token: process.env.API_TOKEN });
```Which renders as:
export const client = createClient({ token: process.env.API_TOKEN });title="…" sets the filename in the header. Without it, the build uses the
language name.
Both Shiki themes ship as CSS variables rather than inlined colours. Switching colour mode therefore needs no re-render, and a light theme’s inline styles never override dark mode.
Code groups
Group alternatives in a <CodeGroup>. Each fence’s title becomes its tab
label. The group gets a single copy button that follows the selected tab.
<CodeGroup>
```bash title="npm"
npm install
```
```bash title="pnpm"
pnpm install
```
</CodeGroup>npm installpnpm installyarn installEvery group that offers the same labels in the same order moves together. The
build remembers the reader’s choice for the next page. Add dropdown when a
horizontal language strip would be crowded. Add sync={false} to keep one
group local.
Diagrams
A ```mermaid fence renders a real Mermaid diagram, with title="…" as
its caption. The build downloads the library only on pages that contain a
diagram. The drawing re-renders when the visitor changes colour mode.
```mermaid title="Publishing a change"
flowchart LR
A["Author edits MDX"] --> B["Preview build"]
B --> C["Production"]
```For diagrams with actions={true}, interactive controls appear on hover or
keyboard focus. These controls give directional movement, zoom, reset, and a
fullscreen viewer with drag, mouse-wheel zoom, and keyboard navigation.
Position the inline controls with placement="top-left", top-right,
bottom-left, or bottom-right. Set actions={false} for a diagram that
should stay static.
A <Mermaid> component also exists, for one-liners and for compatibility with
content authored elsewhere. A fence is the better default choice.
Images
The build handles content images for you. It automatically adds
loading="lazy" and decoding="async". It also adds intrinsic width and
height, read from the file in public/, which is the file that matters.
Without these values, the browser reserves no space, and the page jumps as
each image arrives.
Wrap an image in <Frame> to give it a border and a caption:
<Frame caption="The rollout screen after a guard metric halts a stage.">

</Frame>Icons
Every icon comes from Lucide through <Icon name="…" />.
This applies in the navigation, in buttons, and in any icon slot on a Card
or Tile. Never use a raw emoji or Unicode glyph (✦, ↗, ⌕). These render
without error, but they look inconsistent beside Lucide’s icons, with a
different weight, baseline, and style.
<Card title="Deploy everywhere">
<Icon slot="icon" name="rocket" size={22} />
…
</Card>name is a short alias, not the Lucide icon name directly. See the icons map
in
src/components/Icon.astro.
If the icon you need is not there yet, import it from @lucide/astro and add
one line to that map. Do not use a glyph as a shortcut instead.
Video embeds
The build rewrites a raw <iframe> in content, at build time, into the same
bordered, edge-to-edge card that <Frame> produces. So an embed pasted from
YouTube or Loom gets an intrinsic aspect ratio instead of the browser’s old
default box. This runs as a remark plugin, before the HTML stage, so it also
handles JSX in content authored for other platforms.