Markdown syntax
Live cheatsheet of every markdown feature Publier ships out of the box — GFM (tables, task lists, strikethrough, autolinks, footnotes), frontmatter, directive callouts, and the raw-HTML escape hatch.
A live reference for everything the MDX pipeline understands without extra setup. Each section shows the source on the left of the code fence and the rendered result below.
Frontmatter
Every page starts with a YAML block. Astro reads it into the page’s content collection schema; Publier’s nav, sidebar, and SEO use these fields automatically.
---title: Page titledescription: 1–2 sentence summary used in search + meta description---Defined by remark-frontmatter (auto-wired by Astro’s MDX integration). Add custom fields freely — they survive into the rendered page’s frontmatter export.
Headings and inline code
Long names in headings and inline code wrap to fit the page on narrow screens.
Their text stays unchanged when copied. Fenced code blocks and wide tables keep
horizontal scrolling; component content marked not-prose keeps its own styles.
GFM — tables
Pipe-delimited markdown tables, alignment via :--- / ---: / :---:.
Publier keeps wide tables inside a horizontal scroll region so they fit narrow screens. The original table, headers and alignment stay intact. An overflowing table enters the keyboard tab order; focus it and use the browser’s arrow keys to scroll. A table that fits adds no extra tab stop. Horizontal scrolling also works without JavaScript.
| Feature | Status | Notes || ------------ | :------: | ---------------------- || Tables | shipped | this is one || Task lists | shipped | see below || Strikethrough| shipped | ~~deprecated stuff~~ || Footnotes | shipped | [^1] || Feature | Status | Notes |
|---|---|---|
| Tables | shipped | this is one |
| Task lists | shipped | see below |
| Strikethrough | shipped | |
| Footnotes | shipped | 1 |
GFM — task lists
Tick boxes inside list items.
- [x] Build the parser- [x] Wire up the renderer- [ ] Document the syntax- [ ] Ship to production- Build the parser
- Wire up the renderer
- Document the syntax
- Ship to production
GFM — strikethrough
~~tilde-wrapped~~ text renders with a line through it.
The old API was ~~mostly fine~~ unmaintainable.The old API was mostly fine unmaintainable.
GFM — autolinks
Bare URLs and email addresses get linked automatically — no need to wrap them in <...> or [...](...).
Read more at https://publier.net or mail us at hello@example.com.Read more at https://publier.net or mail us at hello@example.com.
Container directives
Triple-colon blocks parsed by remark-directive and rewritten into Publier’s <Aside> callouts. Four variants ship — note, tip, caution, danger. See the full reference at Callouts.
:::tipUse `bunx publier` to try a one-off command without installing the native CLI.:::Inline directives (:foo[label]{attr=val}) and leaf directives (::break{align=center}) are also parsed, but Publier ships handlers only for the four container types listed above. Any unhandled directive passes through as a plain HTML node — fine for inert markup, never for user input (see Raw HTML below).
Math (opt-in)
KaTeX, behind docsShell({ math: true }). Wrap inline in $...$ or block in $$...$$. Full demo at Math equations.
Raw HTML — what we DON’T do, and why
Publier intentionally does not wire rehype-raw into the pipeline. Raw HTML in MDX is a footgun: a <script> or an onclick= attribute in user-authored content turns into an XSS sink the moment your docs accept community contributions or render markdown from a CMS.
If you control every author and want raw HTML anyway, the safe pattern is the rehype-raw + rehype-sanitize pair:
import { docsShell } from '@publier/shell/integration';import rehypeRaw from 'rehype-raw';import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
export default defineConfig({ integrations: [docsShell()], markdown: { rehypePlugins: [ rehypeRaw, // sanitize-after-parse — never trust raw HTML before it's been allow-listed [rehypeSanitize, { ...defaultSchema, // allow only the tags + attrs you actually need tagNames: [...(defaultSchema.tagNames ?? []), 'iframe'], attributes: { ...defaultSchema.attributes, iframe: ['src', 'title', 'width', 'height', 'allow'], }, }], ], },});Order matters — rehype-raw first to lift HTML into the AST, rehype-sanitize after to strip anything outside the allow-list. Never run rehype-raw without a sanitizer if any of your content comes from untrusted authors.
For most docs sites, the right answer is “use a Publier component instead.” If you find yourself reaching for <iframe>, <details>, or a custom card layout, see Components — most of it is already there.
Pretty quotes / em-dashes (opt-in)
remark-smartypants converts "foo" → “foo” and -- → —. Not auto-wired (it’s stylistic). Add it via markdown.remarkPlugins — see Plugins.
Code fences
Fenced code goes through astro-expressive-code for syntax highlighting, copy buttons, and titles. Full reference at Code blocks. Quick examples:
```ts title="hello.ts"const greet = (name: string) => `Hello, ${name}!`;```const greet = (name: string) => `Hello, ${name}!`;What’s auto-wired vs. what isn’t
| Plugin | Auto-wired | Why |
|---|---|---|
remark-frontmatter | ✓ | Required for every page |
remark-gfm | ✓ | Universal expectation in docs |
remark-directive | ✓ | Powers :::tip callouts |
remark-asides (Publier) | ✓ | Renders directive nodes into <Aside> |
remark-math + rehype-katex | opt-in | Heavy peer deps; only enable when needed |
astro-mermaid | auto | Detected when a mermaid fence appears |
remark-smartypants | — | Stylistic; opinionated |
rehype-raw | — | Security risk by default; opt-in only |
rehype-sanitize | — | Pair with rehype-raw if you opt in |
rehype-format | — | Pretty-prints HTML; cosmetic only, no runtime benefit |
Anything not listed here is still pluggable — append to markdown.remarkPlugins / markdown.rehypePlugins in astro.config.ts. See Plugins for the integration pattern.
Related
- Authoring conventions → Content authoring
- Callouts deep-dive → Callouts
- Code blocks reference → Code blocks
- Custom plugin pipeline → Plugins
Footnotes
-
Footnotes work too —
remark-gfmlifts them to the bottom of the page. ↩