Every construct, in use

A tour of the v0 vocabulary

Every construct in the specification, shown at the size it would really be used rather than as a one-line snippet. On this site each one carries a Markdown tab holding the exact source that produced it, sliced out of this file, so the two can never disagree. The tabs are radio inputs, so nothing here runs a script.

v0 8 constructs No hand-written HTML

Note

What you are looking at. One file. It renders as this page with markset html, as plain CommonMark with markset downgrade, and as itself in a GitHub comment. Nothing below is hand-written HTML, and nothing below is styled by the document: the source names roles, and a stylesheet decides what they look like.

> [!NOTE]
> **What you are looking at.** One file. It renders as this page with `markset html`, as plain CommonMark with `markset downgrade`, and as itself in a GitHub comment. Nothing below is hand-written HTML, and nothing below is styled by the document: the source names roles, and a stylesheet decides what they look like.

metrics

Numbers that have a direction

A table of at least two columns becomes a row of tiles. The first column is the label, the second the value, and an optional third is read as a delta whose sign sets the direction. Below, direction=inverse is doing the work: these are costs, so a fall is the good news and the tiles colour accordingly.

Cost per render
$0.004
-18%
Median build
0.45s
-0.2s
Failed builds
3
+1
:::metrics{direction=inverse}
| Measure | Value | Change |
|---|---|---|
| Cost per render | $0.004 | -18% |
| Median build | 0.45s | -0.2s |
| Failed builds | 3 | +1 |
:::

The same table with no third column gives label and value only, which is the right shape for facts that have no trend.

Spec sections
14
Conformance cases
361
Invalid on purpose
130
:::metrics
| Field | Value |
|---|---|
| Spec sections | 14 |
| Conformance cases | 361 |
| Invalid on purpose | 130 |
:::

grid

A list that reads as a set

grid takes exactly one list and turns its items into cards. That single rule is what makes the fallback exact: remove the fence and a list is still a list, in the same order, with the same content. Items can hold several blocks, so each one can be a small piece of writing rather than a line.

Semantic

Authors name what a thing is. Whether it has a border is the theme's decision, and the document never says.

Portable

Every construct has a defined plain-CommonMark form, so the file still reads where the layout cannot follow.

Checkable

The vocabulary is closed, so a misspelled directive is a reported error rather than something passed through to the page.

:::grid{cols=3}
- ### Semantic

  Authors name what a thing is. Whether it has a border is the theme's decision, and the document never says.

- ### Portable

  Every construct has a defined plain-CommonMark form, so the file still reads where the layout cannot follow.

- ### Checkable

  The vocabulary is closed, so a misspelled directive is a reported error rather than something passed through to the page.
:::

One item can be singled out without touching the others, by opening it with an attribute specifier. The third card below carries a class that this page's stylesheet does not define, which is the point: it reaches the HTML untouched and waits for a theme.

Ordinary item.

Ordinary item.

:::grid{cols=3}
- Ordinary item.
- Ordinary item.
- {.featured}
  Marked item, carrying `.featured` for a stylesheet to find.
:::

columns · card

Regions side by side

columns divides a region with ::col separators. The ratio is the one piece of geometry a Markset document is allowed to carry, because relative column width is genuinely content: it says which side is the main one.

The degradation contract is not aspirational. Each construct has a normative plain-CommonMark form, both forms are covered by the conformance suite, and a mechanical check confirms that a stock CommonMark parser sees the same blocks in the same order.

When these columns are lowered, they are emitted in source order, one after another. That is why the column that must be read first goes first.

Try it

Run markset check on this file, change :::grid to :::widget, and run it again to read the diagnostic.

::::columns{ratio="2:1"}
The degradation contract is not aspirational. Each construct has a normative plain-CommonMark form, both forms are covered by the conformance suite, and a mechanical check confirms that a stock CommonMark parser sees the same blocks in the same order.

When these columns are lowered, they are emitted in source order, one after another. That is why the column that must be read first goes first.

::col

:::card[Try it]{tone=info}
Run `markset check` on this file, change `:::grid` to `:::widget`, and run it again to read the diagnostic.
:::
::::

A card is a titled surface around anything at all. tone picks one of five semantic names, and none of them is a colour.

Neutral

The default, with no tone attribute at all.

:::card[Neutral]
The default, with no tone attribute at all.
:::

Information

For context a reader needs but did not ask for.

:::card[Information]{tone=info}
For context a reader needs but did not ask for.
:::

Caution

For something that will bite later if it is ignored now.

:::card[Caution]{tone=warn}
For something that will bite later if it is ignored now.
:::

tabs

One region, several answers

Tab labels are headings, so a renderer with no tab support shows the sections one after another and loses nothing. The panels switch with radio inputs, which is why this works in a printed page and in an email client.

git clone https://github.com/markset-lang/markset && cd markset
npm install
node packages/cli/src/markset.ts html examples/showcase.md -o showcase.html
import { parseDocument } from "@markset/parser";
import { renderHtml } from "@markset/render-html";

const { ast, diagnostics } = parseDocument(source);
if (diagnostics.some((d) => d.severity === "error")) throw new Error("invalid document");
const html = renderHtml(ast);
node packages/cli/src/markset.ts check docs/*.md
:::tabs
### From source
```sh
git clone https://github.com/markset-lang/markset && cd markset
npm install
node packages/cli/src/markset.ts html examples/showcase.md -o showcase.html
```

### As a library
```js
import { parseDocument } from "@markset/parser";
import { renderHtml } from "@markset/render-html";

const { ast, diagnostics } = parseDocument(source);
if (diagnostics.some((d) => d.severity === "error")) throw new Error("invalid document");
const html = renderHtml(ast);
```

### In CI
```sh
node packages/cli/src/markset.ts check docs/*.md
```
:::

steps

A procedure that stays a procedure

steps takes exactly one ordered list. The numbering belongs to the list rather than to the construct, so the source reads as a procedure even before anything renders it, and a step can carry whatever a real instruction needs.

  1. Declare the version

    Add markset: 0 to the frontmatter. Tools use it to decide that a .md file is meant to be Markset; the parser itself never requires it.

  2. Wrap something you already wrote

    A list becomes a grid, a table becomes metrics. Both keep their meaning when the fence is removed, which is the test a construct has to pass to exist.

    markset check docs/deployment.md
    
  3. Render it

    Every error the validator reports is specified behavior rather than a parser accident, so a document either builds or tells you precisely where it did not.

:::steps
1. ### Declare the version

   Add `markset: 0` to the frontmatter. Tools use it to decide that a `.md` file is meant to be Markset; the parser itself never requires it.

2. ### Wrap something you already wrote

   A list becomes a grid, a table becomes metrics. Both keep their meaning when the fence is removed, which is the test a construct has to pass to exist.

   ```sh
   markset check docs/deployment.md
   ```

3. ### Render it

   Every error the validator reports is specified behavior rather than a parser accident, so a document either builds or tells you precisely where it did not.
:::

figure

Content with a caption

A figure wraps a single image, table or code block and gives it a caption. width accepts percentages only, because the same source has to typeset to print, where a pixel means nothing.

Four boxes, each Markset construct pointing at the CommonMark block it wraps
Every construct wraps a CommonMark primitive, which is what makes the fallback mechanical rather than a matter of taste.
:::figure[Every construct wraps a CommonMark primitive, which is what makes the fallback mechanical rather than a matter of taste.]{#fig-degrade width=80%}
![Four boxes, each Markset construct pointing at the CommonMark block it wraps](degrade.svg)
:::

A table works too. When the content is a table the caption is emitted as the table's own <caption> element, because that is what a screen reader announces as the table's name.

Where each construct's content rule comes from, and what it buys.
Construct Content rule What the rule guarantees
grid Exactly one list The fallback is the list, in order
steps Exactly one ordered list The numbering survives
metrics One table, two columns or more The fallback is a readable table
figure One image, table or code block The caption always has one thing to caption
:::figure[Where each construct's content rule comes from, and what it buys.]
| Construct | Content rule | What the rule guarantees |
|---|---|---|
| `grid` | Exactly one list | The fallback is the list, in order |
| `steps` | Exactly one ordered list | The numbering survives |
| `metrics` | One table, two columns or more | The fallback is a readable table |
| `figure` | One image, table or code block | The caption always has one thing to caption |
:::

callout

Advice that stands apart

Callouts adopt GitHub's alert syntax unchanged, so the bare marker renders natively there. A title on the marker line and a fold suffix are Obsidian's extension; where those are not understood, the callout falls back to a blockquote that still says what it is.

Five types, no more

NOTE, TIP, IMPORTANT, WARNING and CAUTION. Anything else is an error rather than a new kind of callout, which is what a closed vocabulary means in practice.

> [!TIP] Five types, no more
> `NOTE`, `TIP`, `IMPORTANT`, `WARNING` and `CAUTION`. Anything else is an error rather than a new kind of callout, which is what a closed vocabulary means in practice.
The fence length rule

A closing fence closes the innermost open directive. Nesting a three-colon construct inside another means the outer one needs four. This is the rule that catches everyone once.

> [!WARNING] The fence length rule
> A closing fence closes the innermost open directive. Nesting a three-colon construct inside another means the outer one needs four. This is the rule that catches everyone once.
Folded by default

A trailing - collapses a callout and a + expands it. It becomes a <details> element, so folding costs no script. This one started closed.

> [!CAUTION]- Folded by default
> A trailing `-` collapses a callout and a `+` expands it. It becomes a `<details>` element, so folding costs no script. This one started closed.

spans · attribute lines

The two pieces that are not containers

Every section above opens a container with a fence. Two smaller pieces of syntax do the rest of the work, and neither one contains anything: they attach attributes to something that is already there.

A bracketed span attaches them to a run of inline text. It is how a status marker, or a phrase meant to read quietly, gets into the middle of a sentence without a box around it.

Four spans and some ordinary text

Release v0.0.0-rc.2, status Draft, target on track. The words between the markers are plain text, and this phrase is a span as well.

:::card[Four spans and some ordinary text]
Release [v0.0.0-rc.2]{.badge}, status [Draft]{.badge .warn}, target [on track]{.success}. The words between the markers are plain text, and [this phrase]{.muted} is a span as well.
:::

An attribute line attaches them to a block instead. A paragraph, heading, list or table has nowhere to write a specifier of its own, so the specifier goes on a line by itself and applies to whatever starts on the next line. The two columns below hold the same sentence. Only the right one has a line in front of it.

No attribute line here, so this paragraph is set at body size like any other.

An attribute line carrying .lead sits above this one, so it is set larger.

:::columns{ratio="1:1"}
No attribute line here, so this paragraph is set at body size like any other.

::col

{.lead}
An attribute line carrying `.lead` sits above this one, so it is set larger.
:::

Between them, every block and every run of text in a document can be named without a construct existing for it. That is what keeps the vocabulary at eight rather than growing one entry for every typographic need.


Source: examples/showcase.md. The document names no stylesheet of its own; that choice belongs to whoever renders it. Each Markdown tab above is sliced from this file by the position of the construct it sits on, so it is the real source rather than a copy of it.

Examples: all · Analysis document · Strategy memo · Incident review · source