Writing posts Open in Playground

Posts live in src/posts/ (configurable via postsDir). Each post is a single .md file. The filename (minus .md) becomes the slug and the URL path under /posts/.

Frontmatter

src/posts/my-post.md
---
title: A new beginning
date: 2026-04-17
author: Dongsheng
tags: [svelte, web]
category: engineering
excerpt: A short one-liner shown in cards and RSS.
cover: /covers/new-beginning.jpg
draft: false
---

Write markdown below the frontmatter.
md
FieldTypeRequiredNotes
titlestringyesShown in the post hero and list cards.
datestringyesISO date (YYYY-MM-DD). Drives sort order and timeline grouping.
excerptstringrecommendedShort description. Used in cards, RSS, OG. If omitted, the theme derives one from the body.
tagsstring[]—Rendered as pill links below the post title. Each tag gets a /tags/<name>/ page.
categorystring—At most one per post. Gets a /categories/<name>/ page.
authorstring—Overrides the site-wide author.name for this post.
coverstring—Path or URL to a cover image. If set, the post renders with PostCardLarge; if not, the theme picks a tag-hashed gradient.
draftboolean—Drafts are excluded from generated routes, listings, RSS, OG images, and the timeline.

Images and links

Put site-owned images in static/ and reference them with site-absolute paths such as /covers/new-beginning.jpg. The theme prefixes site-absolute cover paths, Markdown image sources, and links in post bodies with SvelteKit's configured base path, so the same post works at the site root and under a subpath such as GitHub Pages. Fully qualified, protocol-relative, fragment, mail, and relative URLs remain unchanged.

Math

Formulas are written in TeX and rendered with KaTeX at build time. Wrap inline math in $…$ and display math in $$…$$:

Binary search runs in $O(\log n)$ time.

$$
\sum_{i=1}^{n} i = \frac{n(n+1)}{2}
$$
md

A line that holds nothing but $$…$$ also becomes a centred display block, even when it directly follows a line of text. $$…$$ that shares its line with other text stays inline.

Code tabs

Group several blocks under one set of tabs with a ::::code-tabs container. Every heading at the level of the first heading inside it starts a tab, and the heading text is the tab label:

::::code-tabs{active="pnpm"}
### npm
```sh
npm install -D @sveltepress/theme-blog
```
### pnpm
```sh
pnpm add -D @sveltepress/theme-blog
```
::::
md

A panel holds whatever follows its heading: usually one code block, but text, images or a nested code-tabs work too. The tabs can be collapsed, and have a copy button.

AttributeEffect
activeLabel of the tab shown first. Defaults to the first tab.
verticalStacks the tab buttons in the top-right corner of the code, and panels slide up and down when switching.
hide-copyRemoves the copy button.

Live code

Add live to a ```svelte block to render the component above its source:

```svelte live
<script>
  let count = $state(0)
</script>

<button onclick={() => count++}>Clicked {count} times</button>
```
md

Add collapsed to start with the source hidden. Any other meta, such as title="…" or line highlighting, applies to the source block. The label of the show/hide toggle is set with liveCode.sourceLabel .

Each block is compiled as its own component with your project's Svelte config, and a post only loads the blocks it uses.

Demo components

Any .svelte file in src/demos/ (configurable via demosDir) can be used in a post as a tag with the file name:

src/demos/Badge.svelte
<script>
  let { ,  = 'info' } = ()
</script>

<span ="badge badge--{}">{}</span>
svelte
src/posts/my-post.md
The build is <Badge text="passing" tone="success" /> again.

<Badge text="On its own line" />
md

Attributes become props: text="x" passes text: 'x', package-name="y" passes packageName: 'y', and a bare attribute such as dev passes dev: true. All values are strings. A tag on its own line is mounted as a block; a tag inside a paragraph, heading or table cell is mounted inline.

Only names that match a file in the demos directory are treated as components; other tags stay plain HTML. Adding or removing a demo file in dev rebuilds the posts.

Listings

  • The home page (/) shows posts in a masonry grid with the newest post featured.
  • /timeline/ shows all non-draft posts grouped by month.
  • /tags/ shows all tags with their counts.
  • /categories/<name>/ and /tags/<name>/ show posts filtered by that taxonomy.

Excerpts

If you don't provide an excerpt, the theme derives one from the first paragraph of the body. For cleaner control, always set excerpt explicitly — especially for posts that open with an image or code block.

Reading time

Reading time is computed automatically from word count and shown in post cards, the timeline, and the post meta strip. Words are counted on the rendered HTML.

Drafts

Set draft: true in frontmatter to exclude a post from the generated site. Drafts are not written to the public post index and their /posts/<slug>/ route is not prerendered, so direct links return 404 in a production build.

Flip draft: false when you're ready to publish.

Related posts

Each post page shows up to a few related posts at the bottom. Relatedness is scored by shared tags, shared category, and year proximity — no config needed.

Last update at: 2026/11/05 09:38