Markdown & Frontmatter
Write content in Markdown with YAML frontmatter for metadata.
File Format
---
title: My First Post
date: 2024-01-15
category: Tech
tags:
- javascript
- web
---
# Hello World
This is my **first post** written in Markdown.
## Code Example
```javascript
console.log('Hello!');More content here...
---
## Frontmatter
The YAML block at the top (between `---` markers) becomes template variables:
```handlebars
<h1>{{title}}</h1>
<time>{{date}}</time>
<span>{{category}}</span>
{{#each tags}}
<span class="tag">{{this}}</span>
{{/each}}Docs Site Navigation (section, order, subcategory)
This documentation site is built with Skier and its sidebar is generated by
generateNavDataTask straight from
frontmatter — there is no hand-maintained nav file. Three fields drive it, and
every page in docs/ sets them so the information architecture stays
predictable. These are the conventions any new docs page must follow.
| Field | Type | Purpose |
|---|---|---|
title |
string | Sidebar label and page <title>. Falls back to the first # H1, then the filename. |
section |
string | Which top-level sidebar group the page belongs to. Must be one of the section names below — spelled and cased exactly. |
order |
number | Sort position within the section (or within its subcategory). Lower = earlier. 0 is conventionally the section's landing/overview page. |
subcategory |
string | (optional) Groups pages into a collapsible sub-list inside a section. Currently used only by Task Reference (Setup / Globals / Content / Feeds & SEO). |
The sections (in sidebar order)
| Section | Contains |
|---|---|
| Getting Started | Install, first build, configuration — the shortest path to a working site. |
| Guides | Task-oriented how-tos and concepts: tasks, custom tasks, templates, frontmatter, architecture, migration, contributing. |
| Task Reference | One page per built-in task, grouped by subcategory. Lives in docs/task-reference/. |
| API Reference | Skier's programmatic surface (types, task contract, config interfaces). Lives in docs/api-reference/. |
| Recipes | End-to-end, copy-paste project examples. |
| FAQ | Common questions. |
The order of the sections themselves is set once, in skier.tasks.cjs
(generateNavDataTask → sectionOrder), not per page. Subcategory order lives
next to it in subcategoryOrder. To add a new section you add pages with that
section: value and give it a rank in sectionOrder; an unranked section
still renders, but sorts last.
Adding a new docs page
- Create the
.mdfile. Put it in the section's folder if it has one (task-reference/,api-reference/); otherwise the flatdocs/root is fine —sectioncomes from frontmatter, not from the folder. - Set
title,section(exactly matching a name above), andorder. - Add
subcategoryonly inside Task Reference. - Rebuild — the sidebar updates itself. No nav file to touch.
A page with no
sectionfalls back to thedefaultSection(Docs) and lands in a stray group, so always set it. Keeping these fields consistent is what lets later content PRs (quickstart, API/task reference) slot in without re-plumbing the nav.
Supported Features
Skier uses marked with GitHub Flavored Markdown:
- Headings:
# H1through###### H6 - Emphasis:
*italic*,**bold**,~~strikethrough~~ - Links:
[text](url) - Images:
 - Code blocks: Triple backticks with language
- Tables: GFM table syntax
- Task lists:
- [ ]and- [x] - Blockquotes:
> - Horizontal rules:
---
Syntax Highlighting
Code blocks are highlighted with highlight.js:
```javascript
const greeting = 'Hello';
console.log(greeting);
```Include the highlight.js CSS in your template:
<link rel="stylesheet" href="https://unpkg.com/highlight.js@11/styles/github-dark.min.css">Code block markup
Each fenced code block is rendered as a <figure class="code-block"> wrapper
so a template's CSS and a small copy-to-clipboard handler have stable hooks —
Skier exposes the metadata but does not hard-code a label chip or copy
button (those belong to your template):
<figure class="code-block" data-language="ts" data-filename="skier.config.ts">
<pre><code class="hljs language-ts">…highlighted…</code></pre>
</figure>data-language— the fence language, present whenever one is given.data-filename— present only when you set a filename (see below).- The
<code>element'stextContentis the original, un-highlighted source, so a copy handler can read it back verbatim with no duplicated payload.
Attach a label with CSS (.code-block[data-filename]::before { content: attr(data-filename); })
and a copy button with a few lines of vanilla JS that read data-* and the
<code> text — no framework, no plugin.
Filenames / titles
Add a title= (or filename=) pair to the fence info string to label a block
with the file it represents:
```ts title="skier.config.ts"
export default { outDir: 'public' };
```Quotes are optional for single-token names (title=deploy.sh) and required for
names with spaces (title="my notes.txt").
Callouts / admonitions
Draw attention with callout blocks using a ::: container. The keyword after
the colons is the callout type; an optional title can follow on the same line:
:::note
A plain note. The body is full markdown — **bold**, links, lists, code.
:::
:::warning Heads up
A warning with a custom title.
:::Supported types: note, tip, info, warning, danger (with caution →
danger and important → info accepted as aliases). An unrecognised keyword
is left as ordinary text, so ::: used for anything else is untouched.
Each block renders as class-driven, semantic markup for the template's CSS to theme per type — Skier hard-codes no colours or icons:
<div class="callout callout-warning" data-callout="warning">
<p class="callout-title">Heads up</p>
<div class="callout-body">…rendered body…</div>
</div>The title defaults to the capitalised type name (Note, Warning, …) when you
don't supply one.
Snippet transclusion (@include)
Pull a slice of a real source or example file into a page at build time, so
code examples can't drift from the code they document. Write an @include
directive on its own line, starting at column 0:
@include examples/quickstart.ts
@include src/config.ts region="setup"
@include src/config.ts lines="10-24"
@include "examples/with spaces.ts" lang="ts" title="config.ts"The file is read at build time and rendered as a normal syntax-highlighted code block (the same markup as a fenced block, so copy buttons and the filename label work identically).
Modifiers (all optional):
| Modifier | Effect |
|---|---|
region="name" |
Only the lines between #region name and #endregion markers (the markers are matched anywhere on a line, so any comment style works). |
lines="a-b" |
A 1-indexed, inclusive line range (or a single line, lines="12"). |
lang="ts" |
Override the highlight language (default: inferred from the file extension). |
title="…" |
Override the filename label (default: the include path). |
Mark a region in the source file with matching comments:
// #region setup
const skier = createSkier({ outDir: 'public' });
// #endregion setupPaths are resolved relative to the project working directory. A missing file,
an unknown region, or an out-of-range line span fails the build with a clear
error — a broken include never silently renders nothing. @include lines
inside a fenced code block are left untouched, so this page can show the syntax
without transcluding itself.
Live example
This page dogfoods the directive. The code block below is not hand-written — it
is transcluded at build time from the snippet-error region of Skier's own
renderer, src/utils/markdown.ts, which is covered by the renderer test suite.
If that class changes or moves, this docs build fails, so the example here can
never drift from the shipping code:
export class SnippetIncludeError extends Error {
constructor(message: string) {
super(`[skier] snippet include: ${message}`);
this.name = 'SnippetIncludeError';
}
}Heading Anchors & Table of Contents
Every rendered heading (## through ######, and #) is given a slugged id,
so any heading is directly linkable — .../my-page/#getting-started. Slugs are
lower-cased with punctuation removed, and duplicates on the same page are made
unique with -2, -3, … suffixes.
The page's headings are also exposed to templates as an ordered headings array
({ level, text, slug } per heading), so you can render an on-page table of
contents. See generateItemsTask → Heading Anchors & On-Page TOC.
Excerpts
For post summaries, use a marker:
---
title: My Post
---
This is the excerpt that appears in lists.
<!--more-->
This is the full content that only appears on the detail page.Configure in your task:
generateItemsTask({
excerptFn: (content) => content.split('<!--more-->')[0],
// ...
})Common Fields
| Field | Type | Usage |
|---|---|---|
title |
string | Page title |
date |
string | ISO date (2024-01-15) |
description |
string | Meta description / excerpt |
tags |
array | Categories/labels |
featured |
boolean | Highlight post |
draft |
boolean | Skip in build |
Tips
- Dates: Use ISO format (
2024-01-15) for reliable parsing - No tabs: YAML requires spaces for indentation
- Optional: Frontmatter block can be omitted if not needed
- Custom fields: Add any field; it's available in templates
Learn More
- Templates & Partials — Using content in templates
- generateItemsTask — Processing Markdown files