Skip to main content
On this page

Markdown Reference

Every feature listed here is enabled by default in @pagesmith/core. No configuration or additional plugins are required unless noted otherwise.

GitHub Flavored Markdown

Enabled via remark-gfm. All GFM extensions are available out of the box.

Tables

Markdown
| Feature       | Syntax        || ------------- | ------------- || Bold          | `**bold**`    || Italic        | `*italic*`    || Strikethrough | `~~deleted~~` |

Alignment is controlled by colons in the separator row:

Markdown
| Left | Center | Right || :--- | :----: | ----: || text |  text  |  text |

Strikethrough

Markdown
~~removed text~~

Task Lists

Markdown
- [x] Completed task- [ ] Pending task- [ ] Another todo

Bare URLs are automatically converted to clickable links:

Markdown
Visit https://example.com for details.

Footnotes

Markdown
This claim needs a source[^1].[^1]: The source for this claim.

The footnote content renders at the bottom of the page with a back-link.

GitHub Alerts

Five alert types are available using blockquote syntax. This is the same format used by GitHub itself.

Markdown
> [!NOTE]> Useful information the reader should know.> [!TIP]> Helpful advice for doing things better.> [!IMPORTANT]> Key information the reader must not miss.> [!WARNING]> Something that needs immediate attention.> [!CAUTION]> Negative potential consequences of an action.
Type Color Use For
[!NOTE] Blue General supplementary information
[!TIP] Green Helpful suggestions and best practices
[!IMPORTANT] Purple Key details the reader must know
[!WARNING] Yellow Things to watch out for
[!CAUTION] Red Dangerous actions or breaking changes

Math

LaTeX math is processed by remark-math (parsing) and rehype-mathjax (SVG rendering). remark-math only runs when markdown.math is true or when the default 'auto' mode detects math markers in the page. MathJax runs before the built-in code renderer so math blocks are not mistakenly treated as code.

Inline Math

Wrap expressions in single dollar signs:

Markdown
The equation $E = mc^2$ changed physics.

Display Math

Wrap expressions in double dollar signs for centered block equations:

Markdown
$$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$

Smart Typography

ASCII punctuation is automatically upgraded to proper typographic characters by remark-smartypants. Code blocks and inline code are never affected.

Input Output Description
"hello" \u201chello\u201d Curly double quotes
'hello' \u2018hello\u2019 Curly single quotes
-- \u2013 En dash
--- \u2014 Em dash
... \u2026 Ellipsis

Any link pointing to an absolute URL (http:// or https://) automatically receives target="_blank" and rel="noopener noreferrer". Internal links (relative paths, anchors) are unaffected.

Markdown
[External](https://example.com) <!-- new tab, noopener -->[Internal](../../guide/getting-started/README.md) <!-- same tab -->[Anchor](#math) <!-- same tab -->

Images

All markdown images are automatically wrapped in <figure class="ps-figure ps-figure-zoomable"> and given a hidden expand button (<button class="ps-img-zoom-btn" hidden data-ps-img-zoom-btn>). Raster images (PNG, JPEG, WebP, GIF) also get a <picture> element with WebP and AVIF <source> variants. SVG images get figure wrapping but no <picture> element. Images inside links are not figure-wrapped and do not receive a zoom button. The image carries data-zoom-src (raster → <stem>.zoom.webp, SVG → original src); themed light/dark pairs carry data-zoom-src-light / data-zoom-src-dark. Pair with @pagesmith/site/runtime/image-zoom for the modal.

The title attribute from markdown syntax becomes a <figcaption>:

Markdown
![Dashboard metrics](./hero.png "Production monitoring dashboard")

Produces:

HTML
<figure class="ps-figure ps-figure-zoomable">  <picture>    <source srcset="./hero.avif" type="image/avif" />    <source srcset="./hero.webp" type="image/webp" />    <img      src="./hero.webp"      alt="Dashboard metrics"      width="..."      height="..."      data-zoom-src="./hero.zoom.webp"      data-zoom-type="image/webp"    />  </picture>  <figcaption>Production monitoring dashboard</figcaption>  <button type="button" class="ps-img-zoom-btn" hidden data-ps-img-zoom-btn>...</button></figure>

Automatic Light/Dark Pair Merging

Consecutive images whose filenames end with -light and -dark suffixes are automatically merged into a single themed figure. No manual HTML is needed:

Markdown
![Architecture overview](./diagrams/arch-light.svg)![Architecture overview](./diagrams/arch-dark.svg)

Produces a <figure class="ps-figure ps-figure-themed ps-figure-zoomable"> with <source media="(prefers-color-scheme: dark)"> so the correct variant displays without JavaScript. The inner <img> carries data-zoom-src-light and data-zoom-src-dark so the zoom modal swaps source on theme change.

Theme-Aware Images

Images and other elements can respond to the active color scheme. The pipeline handles figure wrapping and light/dark pairing automatically for markdown images (see above). Prefer markdown image syntax over raw HTML for all images.

Light/dark image pairs

Place light and dark variants consecutively using standard markdown images. The pipeline auto-merges them:

Markdown
![Architecture overview](./diagrams/arch-light.svg "Build pipeline architecture")![Architecture overview](./diagrams/arch-dark.svg)

The title on the first (light) image becomes the <figcaption>. In color-scheme-auto (the default), the switch follows the OS preference. In explicit light or dark mode, the matching variant is forced.

Generic show/hide helpers

HTML
<span class="show-on-light">Light content</span> <span class="show-on-dark">Dark content</span>

Works on any element, not just images.

Invert on dark

Markdown
![Request path from client through API gateway to database and back](./simple-diagram.invert.svg "Request lifecycle")

Images with .invert. in their filename receive the invert-on-dark class automatically, which applies invert(1) hue-rotate(180deg) in dark mode.

Accessible Emojis

Emoji characters are wrapped in <span role="img" aria-label="..."> so screen readers announce the emoji name.

Markdown
Build complete! 🎉

Produces:

HTML
Build complete! <span role="img" aria-label="party popper">🎉</span>

Heading IDs and Anchors

Every heading gets a URL-safe id (via rehype-slug) and the heading text is wrapped in an anchor link (via rehype-autolink-headings).

Markdown
## Getting Started

Produces:

HTML
<h2 id="getting-started"><a href="#getting-started">Getting Started</a></h2>

These IDs power the table-of-contents sidebar and enable deep-linking to any section.

Code Blocks

Code blocks are rendered by the built-in Pagesmith renderer on top of Shiki with syntax highlighting, dual themes, copy/collapse controls, and shared Pagesmith chrome.

Basic Highlighting

Use a fenced code block with a language identifier:

Markdown
```tsconst greeting = "Hello, world!";```

Over 100 languages are supported via Shiki.

File Titles

Add title="..." to show a filename or label above the block:

Markdown
```ts title="vite.config.ts"import { defineConfig } from "vite";export default defineConfig({});```

Line Numbers

Line numbers are shown by default. Control them per-block:

Markdown
```bash showLineNumbers=falsenpm install @pagesmith/core``````ts startLineNumber=42export function resolve() {  /* ... */}```

Configure the site-wide default:

TypeScript
markdown: {  shiki: { defaultShowLineNumbers: false },}

Line Highlighting

Mark, insert, or delete lines to draw attention:

Markdown
```ts mark={2-3}const name = "Pagesmith";const version = "0.8.0";const highlighted = true;``````ts ins={2} del={1}const old = "before";const updated = "after";```

Range syntax supports individual lines and ranges: mark={1, 3-5, 8}.

Diff Highlighting

Use the diff language for unified diff format:

Markdown
```diff- const port = 3000+ const port = process.env.PORT || 3000```

Collapsible Sections

Hide boilerplate that readers can expand on click:

Markdown
```ts collapse={1-5}import { defineConfig } from "vite";import { pagesmithContent, pagesmithSsg } from "@pagesmith/site/vite";import collections from "./content.config";import path from "node:path";export default defineConfig({  plugins: [pagesmithContent(collections), pagesmithSsg()],});```

Text Wrapping

Enable word wrapping for long lines:

Markdown
```json wrap{  "name": "@pagesmith/core",  "description": "A very long description that would overflow",  "version": "0.8.0"}```

Frame Styles

Terminal languages (bash, sh, zsh, shell, powershell) use a terminal frame. Override explicitly:

Markdown
```bash frame="none"npm install @pagesmith/core```

Values: "code" (editor frame), "terminal", or "plain" (alias "none"). When frame= is omitted, terminal-style languages auto-select "terminal" and everything else auto-selects "code".

Meta String Quick Reference

All properties go after the language identifier in the opening fence:

Property Syntax Description
title title="file.ts" Filename or label above the block
showLineNumbers showLineNumbers=false Show or hide line numbers
startLineNumber startLineNumber=42 Start numbering from a given line
mark mark={3} or mark={1,3-5} Highlight lines (neutral)
ins ins={2-3} Mark lines as inserted (green)
del del={1} Mark lines as deleted (red)
collapse collapse={1-5} Collapse a range of lines
wrap wrap Enable word wrapping
frame frame="terminal" Override the frame style

Combine multiple properties on the same fence:

Markdown
```ts title="example.ts" mark={3} ins={5} collapse={1-2}import { z } from "zod";import { defineCollection } from "@pagesmith/core";const posts = defineCollection({  loader: "markdown",  directory: "content/posts",  schema: z.object({ title: z.string() }),});```

Code Tabs

Consecutive titled code blocks are automatically grouped into a tabbed interface. Write titled fenced blocks one after another with no other content between them:

Markdown
```bash title="npm"npm install @pagesmith/core``````bash title="pnpm"pnpm add @pagesmith/core``````bash title="yarn"yarn add @pagesmith/core``````bash title="bun"bun add @pagesmith/core```

Rendered sample:

npm
npm install @pagesmith/core
pnpm
pnpm add @pagesmith/core
yarn
yarn add @pagesmith/core
bun
bun add @pagesmith/core

The title value becomes the tab label. The first tab is active by default.

Rules

  • Every block in the group must have a title — an untitled block breaks the group.
  • Any non-code content (paragraph, heading, list) between titled blocks breaks the group.
  • Each group is independent; a page can have multiple tab groups.
  • Without JavaScript, all blocks stack vertically as a no-JS fallback.

Multi-Language Example

Markdown
```ts title="TypeScript"interface Config {  host: string;  port: number;}``````python title="Python"@dataclassclass Config:    host: str = "localhost"    port: int = 3000``````go title="Go"type Config struct {    Host string    Port int}```

Rendered sample:

TypeScript
interface Config {  host: string;  port: number;}
Python
@dataclassclass Config:    host: str = "localhost"    port: int = 3000
Go
type Config struct {    Host string    Port int}

Language Aliases

Some languages that Shiki does not recognize natively are aliased to a supported language for highlighting:

Alias Highlighted As
dot text
mermaid text
plantuml text
excalidraw json
drawio xml
proto protobuf
ejs html
hbs handlebars

Add custom aliases via markdown.shiki.langAlias:

TypeScript
markdown: {  shiki: {    langAlias: {      myLang: 'typescript',    },  },}

User aliases take precedence over the defaults.

Dual Themes

Code blocks support light and dark themes simultaneously. The default pair is github-light / github-dark. A prefers-color-scheme media query switches between them automatically.

Configure custom theme pairs:

TypeScript
markdown: {  shiki: {    themes: {      light: 'catppuccin-latte',      dark: 'catppuccin-mocha',    },  },}

Pagesmith maps themes to .color-scheme-light and .color-scheme-dark CSS classes on <html>, so theme switching integrates with the site-wide color scheme toggle.

Custom Plugins

Extend the pipeline with your own remark or rehype plugins:

TypeScript
import remarkToc from "remark-toc";import rehypeFigure from "rehype-figure";const config = defineConfig({  collections: { posts },  markdown: {    remarkPlugins: [remarkToc],    rehypePlugins: [[rehypeFigure, { className: "figure" }]],  },});

Tuple form [plugin, options] is supported for both remark and rehype.

Injection points:

  • Remark plugins run after the built-in remark plugins (GFM, frontmatter, alerts, smartypants, and conditional math) but before remark-rehype.
  • Rehype plugins run after the built-in rehype plugins (built-in code renderer, code tabs, scrollable tables, slug, autolink, external links, emojis, local images) and after the separate heading-extraction pass, but before rehype-stringify.

Content plugins (ContentPlugin.remarkPlugin / ContentPlugin.rehypePlugin) are appended after the config-level plugins.

allowDangerousHtml defaults to true, so raw HTML is preserved unless you explicitly disable it. math defaults to 'auto', which enables remark-math and rehype-mathjax only for content that contains math markers.

Pipeline Order

The full unified pipeline, in execution order:

Text
remark-parse              Parse markdown to MDASTremark-gfm                Tables, strikethrough, task lists, autolinks, footnotesremark-frontmatter        Strip YAML frontmatter from ASTremark-github-alerts      > [!NOTE], > [!TIP], etc.remark-smartypants        Smart quotes, dashes, ellipsesremark-math               Math syntax ($...$, $$...$$) when enabled or auto-detected[user remark plugins]     From MarkdownConfig.remarkPluginslang-alias transform      Map fenced-code language tags via shiki.langAliasremark-rehype             Markdown AST → HTML ASTrehype-mathjax            Render math to SVG (before the built-in code renderer)applyPagesmithCodeRenderer Syntax highlighting, code frames, copy buttonrehype-code-tabs          Group consecutive titled blocks into tabsrehype-scrollable-tables  Wrap markdown tables for horizontal scrollingrehype-slug               Add id="" to headingsrehype-autolink-headings  Wrap heading text in anchor linksrehype-external-links     target="_blank" on external URLsrehype-accessible-emojis  aria-label on emoji charactersrehype-local-images       Figure wrapping, picture element for raster images, light/dark pair mergingheading extraction        Collect headings for TOC data[user rehype plugins]     From MarkdownConfig.rehypePluginsrehype-stringify          HTML AST → HTML string