Skip to content

Examples: Core

The everyday syntax, plus every Tier-1 construct the feature-tier table calls core.

Generated from resources/examples/core.md, resources/examples/edge-cases.md and resources/examples/extensions.md - edit the cases there, not here. Each case links the conformance fixture it produces.

Emphasis

7 conformance fixtures
carve
/italic/  *bold*  /*bold italic*/
_underline_  ~strikethrough~
=highlight=  {^super^}  {,sub,}
html
<p><em>italic</em>  <strong>bold</strong>  <strong><em>bold italic</em></strong>
<u>underline</u>  <s>strikethrough</s>
<mark>highlight</mark>  <sup>super</sup>  <sub>sub</sub></p>

Escapes neutralize delimiters — \/, \*, \_ render as the literal character.

carve
\/literal\/ and \*not bold\*
html
<p>/literal/ and *not bold*</p>

Emphasis nests freely; inner spans render inside outer ones.

carve
*bold with /italic/ inside* and /italic with *bold* inside/
html
<p><strong>bold with <em>italic</em> inside</strong> and <em>italic with <strong>bold</strong> inside</em></p>

Forced intraword emphasis

Wrapping a bare delimiter in a brace pair — {/.../}, {*...*}, {_..._}, {~...~}, {^...^}, {,...,}, {=...=} — forces a span with no word-boundary condition, so it emphasizes intraword. This is the escape hatch for the cases a bare delimiter leaves literal.

carve
foo{*bar*}baz and my{_path_}name and a{/b/}c
html
<p>foo<strong>bar</strong>baz and my<u>path</u>name and a<em>b</em>c</p>

The braces bound the span: a bare same-kind delimiter inside is literal, and cross-type marks nest normally.

carve
{/a/b/} and {/italic *bold*/}
html
<p><em>a/b</em> and <em>italic <strong>bold</strong></em></p>

Highlight is the single-char =; a doubled ==…== is literal by the same-delimiter-adjacency rule.

carve
=marked= here, but ==doubled== is literal.
html
<p><mark>marked</mark> here, but ==doubled== is literal.</p>

{~ … ~} is editorial substitution when it contains a top-level ~>, and forced strikethrough otherwise. {= … =} is forced highlight.

carve
re{~view~} it, then {~old~>new~}, and {=mark=} it.
html
<p>re<s>view</s> it, then <del>old</del><ins>new</ins>, and <mark>mark</mark> it.</p>

Headings

4 conformance fixtures
carve
# Welcome
## Getting started
### Setup
html
<section id="Welcome">
  <h1>Welcome</h1>
  <section id="Getting-started">
    <h2>Getting started</h2>
    <section id="Setup">
      <h3>Setup</h3>
    </section>
  </section>
</section>

All six heading levels are supported.

carve
# H1
## H2
### H3
#### H4
##### H5
###### H6
html
<section id="H1">
  <h1>H1</h1>
  <section id="H2">
    <h2>H2</h2>
    <section id="H3">
      <h3>H3</h3>
      <section id="H4">
        <h4>H4</h4>
        <section id="H5">
          <h5>H5</h5>
          <section id="H6">
            <h6>H6</h6>
          </section>
        </section>
      </section>
    </section>
  </section>
</section>

Attributes attach to the heading via a block-attribute line on the line above (the uniform block rule, §15) — a heading line carries no trailing {…} block. The rendered attribute order matches the source order. An explicit #id hoists to the <section> wrapper.

carve
{#install .featured}
## Setup
html
<section id="install">
  <h2 class="featured">Setup</h2>
</section>

Inline emphasis renders inside heading text.

carve
## Why /Carve/?
html
<section id="Why-Carve">
  <h2>Why <em>Carve</em>?</h2>
</section>
8 conformance fixtures
carve
Read [Djot](https://djot.net) for details.
html
<p>Read <a href="https://djot.net">Djot</a> for details.</p>

A quoted title after the URL becomes the title attribute on the anchor.

carve
[Site](https://example.com "Hover text")
html
<p><a href="https://example.com" title="Hover text">Site</a></p>

Single-quoted titles work too (a deliberate enhancement over djot). A literal apostrophe in a rendered title is escaped to &apos;.

carve
[A](/a 'plain') and [B](/b "Bob's")
html
<p><a href="/a" title="plain">A</a> and <a href="/b" title="Bob&apos;s">B</a></p>

Autolinks use angle brackets and produce a self-titled anchor; bare email addresses get the mailto: scheme.

carve
Visit <https://example.com> or write <hello@example.com>.
html
<p>Visit <a href="https://example.com">https://example.com</a> or write <a href="mailto:hello@example.com">hello@example.com</a>.</p>

A trailing {…} block attaches attributes to an autolink.

carve
<https://example.com>{.ext}
html
<p><a href="https://example.com" class="ext">https://example.com</a></p>

Escaped brackets render as literals, no link is produced.

carve
\[not a link\](https://example.com)
html
<p>[not a link](https://example.com)</p>

A bracketed run directly followed by an attribute block is an inline span (PART 9 §14): the attributes attach to a <span>.

carve
[some text]{.highlight #note key=val}
html
<p><span class="highlight" id="note" key="val">some text</span></p>

Span content is parsed recursively, and an inline link still wins over a span.

carve
[a /b/ c]{.x} and [t](u)
html
<p><span class="x">a <em>b</em> c</span> and <a href="u">t</a></p>

Images

1 conformance fixture
carve
![Apollo 11](apollo.jpg)
html
<img src="apollo.jpg" alt="Apollo 11">

Lists

6 conformance fixtures
carve
- apples
- oranges
- pears
html
<ul>
  <li>apples</li>
  <li>oranges</li>
  <li>pears</li>
</ul>

Ordered lists use N. prefixes — numbering starts from the first marker.

carve
1. first
2. second
3. third
html
<ol>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ol>

Nested lists indent two spaces under the parent item.

carve
- fruit
  - apples
  - oranges
- vegetables
html
<ul>
  <li>fruit
    <ul>
      <li>apples</li>
      <li>oranges</li>
    </ul>
  </li>
  <li>vegetables</li>
</ul>

Lists can mix markers — an ordered list may contain a nested unordered list (and vice versa).

carve
1. setup
   - clone
   - install
2. build
html
<ol>
  <li>setup
    <ul>
      <li>clone</li>
      <li>install</li>
    </ul>
  </li>
  <li>build</li>
</ol>

A task item's content column is the bullet width (2), since the checkbox is content, not marker, so a child indented to column 2 nests. A marker indented below the content column folds in as lazy continuation rather than nesting; no list marker interrupts (§10), so only a marker at or past the content column opens a sub-list.

carve
- [ ] outer
  - inner
html
<ul>
  <li><input type="checkbox" disabled aria-label="outer"> outer
    <ul>
      <li>inner</li>
    </ul>
  </li>
</ul>

Tight nesting is unaffected by the paragraph rule: an indented marker inside an open list item opens a sublist with no blank line, so a one-child nested list still nests.

carve
- parent
  - child
html
<ul>
  <li>parent
    <ul>
      <li>child</li>
    </ul>
  </li>
</ul>

List continuation marker

6 conformance fixtures

A lone + at the list marker column attaches the following flush-left block to the current item, with no blank line, keeping the list tight — useful for code blocks or tables you would rather not indent.

Carve's bullet markers are - and * only. Unlike Markdown and Djot, + is not a bullet in Carve and never has been — it is reserved as the list-continuation marker. This is what makes a lone + unambiguous: there is no + list it could belong to. A + x line is therefore ordinary paragraph text, not a list item.

carve
- Build the image
+
```sh
docker build -t app .
```
- Push it
html
<ul>
  <li>Build the image
    <pre><code class="language-sh">docker build -t app .
</code></pre>
  </li>
  <li>Push it</li>
</ul>

A quote or table attaches the same way.

carve
- item
+
> note
- next
html
<ul>
  <li>item
    <blockquote><p>note</p></blockquote>
  </li>
  <li>next</li>
</ul>

Equivalent to the blank-line form

The continuation marker and the compact blank-line form (above) produce identical output — they are two spellings of the same thing. These are equivalent:

carve
- One

  > Quote
carve
- One
+
> Quote

Both render:

html
<ul>
  <li>One
    <blockquote><p>Quote</p></blockquote>
  </li>
</ul>

Pick whichever reads better. The blank-line form indents the block under the item; the + form marks the attach point with a flush-left marker and keeps the block flush-left — handy for wide code or tables you would rather not indent. The marker must be a lone + at the list marker column with the block flush-left; an indented + is ordinary text, not a continuation marker.

First block of an item

Put the marker and a lone + on the same line — - + — to start an item directly with a block, with the block body flush-left (no indentation). The item has no lead text; its whole content is the following block.

carve
- +
| a | b |
| c | d |
- next
html
<ul>
  <li>
    <table>
      <tbody>
        <tr><td>a</td><td>b</td></tr>
        <tr><td>c</td><td>d</td></tr>
      </tbody>
    </table>
  </li>
  <li>next</li>
</ul>

A lone + after the marker is the continuation marker, not text. - + text (with content after the +) keeps + text as literal item text — only a bare + triggers the first-block form.

Since + is not a Carve bullet (use - or *), the lines below are a single paragraph, not a two-item list — the same input is a bullet list in Markdown and Djot, but not in Carve.

carve
+ one
+ two
html
<p>+ one
+ two</p>

A sub-list's marker column takes the marker too

"The current container" in §17 L3 is whichever container the marker's column belongs to, and inside an item that can be a sub-list: a + at the sub-list's marker column attaches to the sub-list's item, not to the outer one. What it attaches is the flush-left block, and both spellings are pinned in "A continuation marker attaches only a flush-left block" below.

The plainest form of the rule, at a single level: the attached block is written flush left, and the item holds it without a blank line.

carve
- a
+
c
html
<ul>
  <li>a
    c
  </li>
</ul>

Indent it one step further and it is past every marker column in scope, so it is ordinary text again — the same rule as above, read against the sub-list instead of the outer item:

carve
- a
  - b
    +
    c
html
<ul>
  <li>a
    <ul>
      <li>b
+
c</li>
    </ul>
  </li>
</ul>

List item attributes

8 conformance fixtures

An attribute block that abuts a list marker (no space between the marker and {) attaches its attributes to the <li> itself. The marker's required space follows the block (grammar item_attributes, PART 9 §15). This works for bullet and ordered markers alike:

carve
-{.c} A classed item.
-{#intro} An item with an id.
html
<ul>
  <li class="c">A classed item.</li>
  <li id="intro">An item with an id.</li>
</ul>

Ordered markers carry the abutting block the same way, before the required space, in every dialect:

carve
3.{#x k=v} A numbered item with id and key-value.
html
<ol start="3">
  <li id="x" k="v">A numbered item with id and key-value.</li>
</ol>
carve
a.{.c} An alpha item.
html
<ol type="a">
  <li class="c">An alpha item.</li>
</ol>

For a task item the block abuts the marker, before the task marker:

carve
-{.c} [ ] A classed task item.
html
<ul>
  <li class="c"><input type="checkbox" disabled aria-label="A classed task item."> A classed task item.</li>
</ul>

The empty block {} is a blessed exception: it yields a bare <li> (so a default-attribute processor can target the item):

carve
-{} A bare item via the empty block.
html
<ul>
  <li>A bare item via the empty block.</li>
</ul>

The abutting block is consumed as list-item attributes only when it yields at least one attribute or is the blessed empty block. A block that is not an attribute block (for example a forced {+…+} emphasis span) leaves the -{ as ordinary text, so no list opens:

carve
-{+a+} text
html
<p>-<ins>a</ins> text</p>

A space before the brace makes the block ordinary item content, not a list-item attribute. Because no inline element abuts it, the block is not an attribute block at all: the braces stay literal (grammar PART 9 §14, inline_span requires a [...] host):

carve
- {.c} text
html
<ul>
  <li>{.c} text</li>
</ul>

The same rule holds anywhere in inline content: a {...} block with no abutting host (at the start of the content, or after whitespace) is literal text, never silently dropped:

carve
para {.c} more
html
<p>para {.c} more</p>

Task lists

2 conformance fixtures
carve
- [ ] todo
- [x] done
html
<ul>
  <li><input type="checkbox" disabled aria-label="todo"> todo</li>
  <li><input type="checkbox" checked disabled aria-label="done"> done</li>
</ul>

Only [x]/[X] render a checked box; every other state ([ ], [-], [_], [>], [?]) renders an unchecked box. The four extended states name the box they carry with data-task-state, so a stylesheet can tell a dropped task from an open one; [ ] and [x] carry nothing, because the box already says which they are.

carve
- [-] dropped
- [_] paused
- [>] deferred
- [?] maybe
html
<ul>
  <li data-task-state="-"><input type="checkbox" disabled aria-label="dropped"> dropped</li>
  <li data-task-state="_"><input type="checkbox" disabled aria-label="paused"> paused</li>
  <li data-task-state="&gt;"><input type="checkbox" disabled aria-label="deferred"> deferred</li>
  <li data-task-state="?"><input type="checkbox" disabled aria-label="maybe"> maybe</li>
</ul>

Blockquote with attribution

1 conformance fixture
carve
> Stay hungry, stay foolish.
^ Steve Jobs
html
<figure>
  <blockquote><p>Stay hungry, stay foolish.</p></blockquote>
  <figcaption>Steve Jobs</figcaption>
</figure>

Block-quote continuation marker

2 conformance fixtures

The continuation marker generalizes to block quotes (grammar PART 9 §17): a lone + at column 0 immediately after a quoted line attaches the following flush-left block to the quote — the un-prefixed analogue of the list-item form, so a real block joins the quote without repeating > on every line.

carve
> quoted
+
- item
html
<blockquote>
  <p>quoted</p>
  <ul>
    <li>item</li>
  </ul>
</blockquote>

It only attaches: a blank line still ends the quote and starts a sibling, and a + outside any container is literal text. A > line after the attached block resumes the quote.

carve
> quoted
+
- item
> more
html
<blockquote>
  <p>quoted</p>
  <ul>
    <li>item</li>
  </ul>
  <p>more</p>
</blockquote>

Image with caption

3 conformance fixtures
carve
![Apollo 11](apollo.jpg)
^ Figure 1: First moon landing
html
<figure>
  <img src="apollo.jpg" alt="Apollo 11">
  <figcaption>Figure 1: First moon landing</figcaption>
</figure>

A trailing attribute block is the image's attribute, so it stays on the <img> even when the image is wrapped in a <figure>, the same target as a standalone block image. To attribute the <figure> instead, use a preceding block-attribute line, which floats onto the outer block (§15).

carve
![Apollo 11](apollo.jpg){.hero}
^ Figure 1: First moon landing
html
<figure>
  <img src="apollo.jpg" alt="Apollo 11" class="hero">
  <figcaption>Figure 1: First moon landing</figcaption>
</figure>
carve
{.gallery}
![Apollo 11](apollo.jpg)
^ Figure 1: First moon landing
html
<figure class="gallery">
  <img src="apollo.jpg" alt="Apollo 11">
  <figcaption>Figure 1: First moon landing</figcaption>
</figure>

Tables

6 conformance fixtures
carve
|= Fruit |= Price |
| Apple  | $1     |
| Pear   | $2     |
^ Fruit prices
html
<table>
  <caption>Fruit prices</caption>
  <thead>
    <tr><th scope="col">Fruit</th><th scope="col">Price</th></tr>
  </thead>
  <tbody>
    <tr><td>Apple</td><td>$1</td></tr>
    <tr><td>Pear</td><td>$2</td></tr>
  </tbody>
</table>

Single-column tables follow the same rules — one |= cell yields the header row.

carve
|= Heading |
| Row 1    |
| Row 2    |
html
<table>
  <thead>
    <tr><th scope="col">Heading</th></tr>
  </thead>
  <tbody>
    <tr><td>Row 1</td></tr>
    <tr><td>Row 2</td></tr>
  </tbody>
</table>

A GFM-style separator row (the second row, all dashes with optional alignment colons) is also accepted: it makes the first row the header and sets per-column alignment.

carve
| Name | Age |
|:-----|----:|
| Alice | 28  |
html
<table>
  <thead>
    <tr><th scope="col" style="text-align: left;">Name</th><th scope="col" style="text-align: right;">Age</th></tr>
  </thead>
  <tbody>
    <tr><td style="text-align: left;">Alice</td><td style="text-align: right;">28</td></tr>
  </tbody>
</table>

Inline emphasis applies inside cells just like in paragraphs.

carve
|= Style    |= Sample      |
| italic    | /soft/        |
| strong    | *firm*        |
| code      | `literal`     |
html
<table>
  <thead>
    <tr><th scope="col">Style</th><th scope="col">Sample</th></tr>
  </thead>
  <tbody>
    <tr><td>italic</td><td><em>soft</em></td></tr>
    <tr><td>strong</td><td><strong>firm</strong></td></tr>
    <tr><td>code</td><td><code>literal</code></td></tr>
  </tbody>
</table>

A |= cell in a body row is a row header: it renders as <th scope="row"> inside <tbody> while the row stays a body row. This expresses row headers (a leading first-column <th scope="row"> per data row), which a separator row cannot. The thead is still only the leading all-header rows.

carve
|=         |= Diameter (km) |= Size vs Earth |
|= Mercury | 4,879.4         | 38%            |
|= Venus   | 12,104          | 95%            |
html
<table>
  <thead>
    <tr><th scope="col"></th><th scope="col">Diameter (km)</th><th scope="col">Size vs Earth</th></tr>
  </thead>
  <tbody>
    <tr><th scope="row">Mercury</th><td>4,879.4</td><td>38%</td></tr>
    <tr><th scope="row">Venus</th><td>12,104</td><td>95%</td></tr>
  </tbody>
</table>

With no leading header row, every first cell can still be a row header — the table has no <thead> at all.

carve
|= Mercury | 4,879 |
|= Venus   | 12,104 |
html
<table>
  <tbody>
    <tr><th scope="row">Mercury</th><td>4,879</td></tr>
    <tr><th scope="row">Venus</th><td>12,104</td></tr>
  </tbody>
</table>

Table column alignment

1 conformance fixture
carve
|= Name |=> Age |=~ City |
| Alice  | 28     | NYC     |
| Bob    | 34     | London  |
html
<table>
  <thead>
    <tr><th scope="col">Name</th><th scope="col" style="text-align: right;">Age</th><th scope="col" style="text-align: center;">City</th></tr>
  </thead>
  <tbody>
    <tr><td>Alice</td><td style="text-align: right;">28</td><td style="text-align: center;">NYC</td></tr>
    <tr><td>Bob</td><td style="text-align: right;">34</td><td style="text-align: center;">London</td></tr>
  </tbody>
</table>

Table per-cell alignment override

1 conformance fixture
carve
|= Item     |=> Qty |
| Apple      | 12     |
| Subtotal   |< 12    |
html
<table>
  <thead>
    <tr><th scope="col">Item</th><th scope="col" style="text-align: right;">Qty</th></tr>
  </thead>
  <tbody>
    <tr><td>Apple</td><td style="text-align: right;">12</td></tr>
    <tr><td>Subtotal</td><td style="text-align: left;">12</td></tr>
  </tbody>
</table>

Headerless table alignment

1 conformance fixture
carve
| a |> 9  |
| b |> 10 |
html
<table>
  <tbody>
    <tr><td>a</td><td style="text-align: right;">9</td></tr>
    <tr><td>b</td><td style="text-align: right;">10</td></tr>
  </tbody>
</table>

Table multi-line cell continuation

1 conformance fixture

A + line continues the previous row's cells, so a logical cell can span several source lines.

carve
|= Feature |= Description        |
| Complex  | A long description |
+          | that continues     |
+          | across lines.      |
| Simple   | Single line.       |
html
<table>
  <thead>
    <tr><th scope="col">Feature</th><th scope="col">Description</th></tr>
  </thead>
  <tbody>
    <tr><td>Complex</td><td>A long description that continues across lines.</td></tr>
    <tr><td>Simple</td><td>Single line.</td></tr>
  </tbody>
</table>

Tables with rowspan and colspan

1 conformance fixture
carve
|= Category |= Item    |= Price |
| Fruit     | Apple    | $1     |
| ^         | Banana   | $0.50  |
| Total     | <        | $1.50  |
html
<table>
  <thead>
    <tr><th scope="col">Category</th><th scope="col">Item</th><th scope="col">Price</th></tr>
  </thead>
  <tbody>
    <tr><td rowspan="2">Fruit</td><td>Apple</td><td>$1</td></tr>
    <tr><td>Banana</td><td>$0.50</td></tr>
    <tr><td colspan="2">Total</td><td>$1.50</td></tr>
  </tbody>
</table>

Fenced code

13 conformance fixtures
carve
```python
print("hi")
```
html
<pre><code class="language-python">print("hi")
</code></pre>

A fenced block with no info string renders without a language class.

carve
```
plain text
```
html
<pre><code>plain text
</code></pre>

A code fence carries no inline attributes — the info string is just the language. Attributes on a code block use the standard preceding {…} block-attribute line; they render on the <pre> (the language stays language-… on the <code>).

carve
{.fancy #x}
```php
code
```
html
<pre class="fancy" id="x"><code class="language-php">code
</code></pre>

The info string may carry a bracketed [label] after the language (or a bare [label] with no language). The label is structured metadata — it is not part of the language class; the core renderer ignores it, and an extension (e.g. a code-group) may use it.

carve
```php [NPM]
npm install x
```
html
<pre><code class="language-php">npm install x
</code></pre>

A quoted "header" after the language (and before any [label]) sets a human-visible title for the block. Because a code block's <pre><code> holds atomic preformatted text, the header cannot be a child element the way an admonition title is — core carries it as the title attribute on the <pre>, and the host decides whether to render a filename bar or leave it as the native mouseover tooltip. It uses the same quoted-title token as an admonition header, but because it targets an attribute the text is literal (not inline-parsed), only HTML-escaped — so markup-like characters in a filename survive.

carve
```php "src/Auth.php"
$ok = true;
```
html
<pre title="src/Auth.php"><code class="language-php">$ok = true;
</code></pre>

A header and a [label] may combine, in that fixed order. The label stays inert in core (a code-group would use it as the tab name); the header still becomes the title.

carve
```php "src/Auth.php" [Composer]
composer require x
```
html
<pre title="src/Auth.php"><code class="language-php">composer require x
</code></pre>

A header may appear with no language, leaving the <code> unclassed.

carve
``` "notes.txt"
remember the milk
```
html
<pre title="notes.txt"><code>remember the milk
</code></pre>

The header text is literal — markup-like characters (a glob *, an underscore) are not parsed, so a filename survives intact in the title.

carve
```js "*.config.js"
export default {}
```
html
<pre title="*.config.js"><code class="language-js">export default {}
</code></pre>

Anything else after the language token — a bare second word, a key="value" pair, an inline {…} block, or a header and label in the wrong order — is not a fenced code block. There is no error: the backtick run falls back to ordinary inline parsing (an inline code span). Quotes and brackets are the only delimiters that admit metadata, and only in the order header-then-label.

carve
```js title="x"
code
```
html
<p><code>js title="x"
code
</code></p>
carve
```php [Composer] "x"
code
```
html
<p><code>php [Composer] "x"
code
</code></p>

Tildes are an alternative fence — useful when the body contains backtick fences.

carve
~~~yaml
key: value
~~~
html
<pre><code class="language-yaml">key: value
</code></pre>

Lengthening the fence lets a code block embed a literal triple-backtick fence as content.

carve
````markdown
```python
print("hi")
```
````
html
<pre><code class="language-markdown">```python
print("hi")
```
</code></pre>

Code-block content is never parsed for Carve syntax — emphasis, headings, and tags inside are literal.

carve
```
# not a heading
/not italic/  *not bold*  #notatag
```
html
<pre><code># not a heading
/not italic/  *not bold*  #notatag
</code></pre>

Inline code

4 conformance fixtures
carve
Run `npm install` first.
html
<p>Run <code>npm install</code> first.</p>

Use a longer run of backticks to embed a literal backtick inside the span.

carve
The literal `` ` `` is one backtick.
html
<p>The literal <code>`</code> is one backtick.</p>

Carve syntax inside a code span is never parsed — it renders as literal text.

carve
The string `*not bold*` is literal.
html
<p>The string <code>*not bold*</code> is literal.</p>

A pipe inside an inline code span does not split the surrounding table cell.

carve
Use `ls | grep foo` to filter.
html
<p>Use <code>ls | grep foo</code> to filter.</p>

Attributes

5 conformance fixtures
carve
{.large #intro}
# Title

A paragraph with [a styled link](url){.btn .primary}.
html
<section id="intro">
  <h1 class="large">Title</h1>
  <p>A paragraph with <a href="url" class="btn primary">a styled link</a>.</p>
</section>

An inline {...} attaches to the preceding inline node — including an inline code span. (The {=html} / {=latex} raw-inline form is a separate rule.)

carve
`code`{.cls}
html
<p><code class="cls">code</code></p>

A {...} line on its own attaches to the next block (PART 9 §15).

carve
{.note}
This paragraph gets the class.
html
<p class="note">This paragraph gets the class.</p>

Block attributes attach to any block — here, a list.

carve
{.todo}
- one
- two
html
<ul class="todo">
  <li>one</li>
  <li>two</li>
</ul>

Attributes render in the order written in the source — classes merge into one class at the first class's position (PART 9 attributes rule).

carve
[label]{key=c .a #b}
html
<p><span key="c" class="a" id="b">label</span></p>

Frontmatter

3 conformance fixtures
carve
---
title: My Document
author: Jane Doe
date: 2026-03-15
---

Content begins here.
html
<p>Content begins here.</p>

The opening delimiter may name the metadata format (---yaml, ---json, ---toml, ---neon, …); a bare --- defaults to YAML. Either way the frontmatter is metadata, not rendered. The closing delimiter is always a bare ---.

carve
---json
{"title": "My Document"}
---

Content begins here.
html
<p>Content begins here.</p>

The space between --- and the format token is optional — ---toml and --- toml are both accepted (the no-space form is canonical), matching code fences: ```php is canonical, though a space after the fence is accepted for compatibility. What is accepted there is exactly one literal space: a tab before the format token is not that separator, so the line is not a delimiter at all. A whitespace run with NOTHING after it is a different question: it is trailing whitespace on a content line, it is dropped, and the bare opener it leaves behind opens normally. Both positions are pinned in the edge cases, for the delimiter and for the code fence alike.

carve
--- toml
title = "My Document"
---

Content begins here.
html
<p>Content begins here.</p>

Heading IDs

3 conformance fixtures

A heading's id is derived from its text content. An inline either contributes the literal text it carries or contributes nothing, and which of the two it does is a property of the CONSTRUCT rather than of what it renders: an id is assigned before a cross-reference resolves or a symbol is looked up. A code span, a math run, an image's alt text, a link's label and a superscript contribute; a footnote reference, a cross-reference and a symbol shortcode do not, and a line comment ends the line it sits on. Each of those is pinned in the edge cases.

Heading ids are case-preserving by default and apply no Unicode normalization: a heading keeps its original case and any non-ASCII characters verbatim. Cross-references resolve case-insensitively, so a lowercase </#getting-started> still points at a Getting-Started heading.

carve
# Café Notes

# Über uns

# 2024 Recap

## Setup

## Setup

{#api-v2}
# API

See </#cafe-notes>, </#section-2024-recap>, </#setup-2>, and </#api-v2>.
html
<section id="Café-Notes">
  <h1>Café Notes</h1>
</section>
<section id="Über-uns">
  <h1>Über uns</h1>
</section>
<section id="s-2024-Recap">
  <h1>2024 Recap</h1>
  <section id="Setup">
    <h2>Setup</h2>
  </section>
  <section id="Setup-2">
    <h2>Setup</h2>
  </section>
</section>
<section id="api-v2">
  <h1>API</h1>
  <p>See &lt;/#cafe-notes&gt;, &lt;/#section-2024-recap&gt;, <a href="#Setup-2">Setup</a>, and <a href="#api-v2">API</a>.</p>
</section>

A cross-reference matches its target case-insensitively and links to the target's actual (case-preserved) id, so the reference can be written in lowercase regardless of how the heading is capitalized.

carve
# Getting Started

Jump to </#getting-started>.
html
<section id="Getting-Started">
  <h1>Getting Started</h1>
  <p>Jump to <a href="#Getting-Started">Getting Started</a>.</p>
</section>

Non-ASCII symbols, marks, and punctuation are kept verbatim; only runs of ASCII non-alphanumerics collapse to a single hyphen.

carve
# Café Crème

# Hello • World

# 中文、标题
html
<section id="Café-Crème">
  <h1>Café Crème</h1>
</section>
<section id="Hello-•-World">
  <h1>Hello • World</h1>
</section>
<section id="中文、标题">
  <h1>中文、标题</h1>
</section>
3 conformance fixtures

[text][label] resolves against a [label]: url "title" definition anywhere in the document (order-independent). The definition line itself produces no output.

carve
Read the [introduction][intro] first.

[intro]: https://example.com/intro "Introduction"
html
<p>Read the <a href="https://example.com/intro" title="Introduction">introduction</a> first.</p>

A trailing attribute block attaches to the resolved <a>, the same slot an inline link uses (grammar reference_link).

carve
Read the [intro][x]{.ext} first.

[x]: /intro
html
<p>Read the <a href="/intro" class="ext">intro</a> first.</p>

A quoted run after the destination is still parsed as the title, exactly as in an inline link.

carve
[r][r]

[r]: /url "Title"
html
<p><a href="/url" title="Title">r</a></p>
2 conformance fixtures

[text][] uses the link text as the label.

carve
See [Other Page][] for details.

[Other Page]: /other-page
html
<p>See <a href="/other-page">Other Page</a> for details.</p>

A trailing attribute block attaches to the resolved <a> here too (grammar collapsed_reference_link).

carve
See [Other][]{.ext} for details.

[Other]: /other
html
<p>See <a href="/other" class="ext">Other</a> for details.</p>

Smart typography dashes and quotes

1 conformance fixture

-- --- ... become en/em dashes and ellipsis; straight quotes become contextual curly quotes.

carve
He paused -- then ran --- fast... "Stop!" it's over.
html
<p>He paused – then ran — fast… “Stop!” it’s over.</p>

Smart typography arrows and symbols

1 conformance fixture

Arrows, comparisons, plus/minus and symbols are converted. Fractions are intentionally not converted (they collide with dates and paths; see docs/dismissed-syntax.md).

carve
Flow: a --> b <-- c <--> d ==> e; x != y, p <= q, r >= s, +-1.
(c) 2024, (r), (tm). Dates like 1/2/2024 stay literal.
html
<p>Flow: a → b ← c ↔ d ⇒ e; x ≠ y, p ≤ q, r ≥ s, ±1.
© 2024, ®, ™. Dates like 1/2/2024 stay literal.</p>

Math

2 conformance fixtures

Inline math is $`…` and display math $$`…`. Wrapping the content in a backtick span removes any ambiguity with a literal $, so currency stays literal. The output matches djot.

carve
Inline $`E = mc^2` and currency $5 stays literal.

$$`\int_0^1 x\,dx`
html
<p>Inline <span class="math inline" role="math">\(E = mc^2\)</span> and currency $5 stays literal.</p>
<p><span class="math display" role="math">\[\int_0^1 x\,dx\]</span></p>

A trailing attribute block applies to the math span, merging classes into the existing math inline / math display class (math reuses the code-span attribute slot). The {=format} raw form is code-span-only and is not inherited by math: $`x`{=html} leaves the {=html} literal.

carve
$`a^2`{.boxed #eq1 data-k=v}
html
<p><span class="math inline boxed" id="eq1" data-k="v" role="math">\(a^2\)</span></p>

Footnotes

4 conformance fixtures

A [^label] reference is numbered by document order; its [^label]: … definition renders in an endnotes section with a backlink, using djot's doc-noteref / doc-endnotes / doc-backlink roles.

The label is a non-empty physical-line identifier. Spaces and tabs are allowed and matched exactly, but a source newline ends the opportunity to form the reference: an editor's visual wrapping is harmless, while an inserted hard line break leaves the bracketed source literal. A definition marker likewise occupies one physical line, so there is no multiline identifier that only one side could produce.

carve
Carve has footnotes.[^fn]

[^fn]: Defined anywhere; resolved by label.
html
<p>Carve has footnotes.<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a></p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>Defined anywhere; resolved by label.<a href="#fnref1" role="doc-backlink" aria-label="Back to reference"></a></p>
    </li>
  </ol>
</section>

A reference definition is invisible metadata, so it still ends the paragraph even with no blank line (§10); indented lines continue the note body.

carve
See the note[^m].
[^m]: First line of the note
   and a continuation line.
html
<p>See the note<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a>.</p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>First line of the note
and a continuation line.<a href="#fnref1" role="doc-backlink" aria-label="Back to reference"></a></p>
    </li>
  </ol>
</section>

A trailing attribute block on a reference attaches to the noteref <a> (grammar PART 9 §16). Only the reference where the author wrote the block carries it.

carve
Text[^a]{.ref}.

[^a]: note.
html
<p>Text<a id="fnref1" href="#fn1" role="doc-noteref" class="ref"><sup>1</sup></a>.</p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>note.<a href="#fnref1" role="doc-backlink" aria-label="Back to reference"></a></p>
    </li>
  </ol>
</section>

A note referenced more than once gets a distinct fnref id per reference and one numbered backlink per reference ( with a superscript), so each return arrow points back to its own reference. (A note referenced once keeps a plain .)

carve
See[^m] and again[^m].

[^m]: One note, two refs.
html
<p>See<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a> and again<a id="fnref1-2" href="#fn1" role="doc-noteref"><sup>1</sup></a>.</p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>One note, two refs.<a href="#fnref1" role="doc-backlink" aria-label="Back to reference 1"><sup>1</sup></a> <a href="#fnref1-2" role="doc-backlink" aria-label="Back to reference 2"><sup>2</sup></a></p>
    </li>
  </ol>
</section>

Inline footnotes

2 conformance fixtures

An inline footnote ^[content] carries its note text in place (pandoc-style), with no separate definition. It is numbered into the same endnotes section as a reference footnote, interleaved by document order, and its content is inline (§16). A caret immediately before [ opens the note; any other caret is literal text (there is no bare superscript). ^[x]^ is therefore a note plus a literal ^, ^^[x] is a literal ^ plus a note, and \^[x] is literal.

carve
A note^[see *later*] inline. And a ref[^a].

[^a]: reference body.
html
<p>A note<a id="fnref1" href="#fn1" role="doc-noteref"><sup>1</sup></a> inline. And a ref<a id="fnref2" href="#fn2" role="doc-noteref"><sup>2</sup></a>.</p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>see <strong>later</strong><a href="#fnref1" role="doc-backlink" aria-label="Back to reference"></a></p>
    </li>
    <li id="fn2">
      <p>reference body.<a href="#fnref2" role="doc-backlink" aria-label="Back to reference"></a></p>
    </li>
  </ol>
</section>

A trailing attribute block attaches to the noteref <a>, like a reference footnote (§16).

carve
Text^[note]{.ref}.
html
<p>Text<a id="fnref1" href="#fn1" role="doc-noteref" class="ref"><sup>1</sup></a>.</p>
<section role="doc-endnotes" aria-label="Footnotes">
  <hr>
  <ol>
    <li id="fn1">
      <p>note<a href="#fnref1" role="doc-backlink" aria-label="Back to reference"></a></p>
    </li>
  </ol>
</section>

Generic divs

2 conformance fixtures

A bare ::: opener with no type word is djot's generic container: a plain <div> (a typed ::: word is a two-tier admonition/div instead). The fence line carries no inline attributes (strict djot); to attribute a div, put a {…} block-attribute line before the opener, which floats onto it.

carve
:::
A plain box.
:::

{#s .sidebar}
:::
A div with attributes.
:::
html
<div>
  <p>A plain box.</p>
</div>
<div id="s" class="sidebar">
  <p>A div with attributes.</p>
</div>

A bare colon fence closes a container only when it is EXACTLY as long as that container's opener, so the run length is a local depth count: the outermost container is ::: and every level inward adds a colon. That is the direction carve fmt emits, and it is writable from the top down - a fence is sized by the levels above it, which are already on the page. The other direction parses too; equal-length fences nest as well, since a fence carrying a type word is never a closer.

carve
::: outer

:::: middle

::::: note
X
:::::

::::

:::
html
<div class="outer">
  <div class="middle">
    <aside class="admonition note" aria-label="Note">
      <p>X</p>
    </aside>
  </div>
</div>

Nested containers

5 conformance fixtures

A bare colon fence closes a container only when it is EXACTLY as long as that container's opener. Nesting therefore needs the two fences to differ, and the canonical direction is one colon wider per level inward. A longer-outer document like the one below parses too - exact matching does not care which way the lengths run.

carve
:::: note
Outer.

::: tip
Nested.
:::
::::
html
<aside class="admonition note" aria-label="Note">
  <p>Outer.</p>
  <aside class="admonition tip" aria-label="Tip">
    <p>Nested.</p>
  </aside>
</aside>

Equal-length fences nest. ::: tip is not a closer - a closer is bare - so it opens a container inside the note, and the two bare fences close them innermost-first.

carve
::: note
::: tip
Inner.
:::
:::
html
<aside class="admonition note" aria-label="Note">
  <aside class="admonition tip" aria-label="Tip">
    <p>Inner.</p>
  </aside>
</aside>

Widening the fence for the deeper level is the canonical direction. A bare :::: does not match the open :::, so it is not a closer; it opens a child. This is the form carve fmt emits.

carve
:::
Outer

::::
Inner
::::
:::
html
<div>
  <p>Outer</p>
  <div>
    <p>Inner</p>
  </div>
</div>

An opener always opens. A container still open at the end of the input closes there, so a forgotten closer costs you the container's extent, not the rest of the document. Lint and the language server flag it.

carve
::: note
X
html
<aside class="admonition note" aria-label="Note">
  <p>X</p>
</aside>

One closer closes one container, not every container open above it. Here the ::: closes c; a and b have no closer of their own and close at the end of the input by the rule above. Djot's bare closer instead closes every open container of equal-or-lesser length in one go.

carve
::::: a
:::: b
::: c
X
:::
html
<div class="a">
  <div class="b">
    <div class="c">
      <p>X</p>
    </div>
  </div>
</div>

Definition lists

6 conformance fixtures

:: term (one or more) then : definition (one or more) form an entry, rendered as a <dl> of <dt> then <dd>. Two colons is a term; three is a div/admonition.

carve
:: color
:: colour
: The visual property of objects.
: A pigment or paint.
html
<dl>
  <dt>color</dt>
  <dt>colour</dt>
  <dd>The visual property of objects.</dd>
  <dd>A pigment or paint.</dd>
</dl>

A definition continues exactly like a list item. An indented block after a blank line folds into the definition, so a <dd> can hold more than one paragraph:

carve
:: term
: A definition can now hold

  more than one paragraph.
html
<dl>
  <dt>term</dt>
  <dd>
    <p>A definition can now hold</p>
    <p>more than one paragraph.</p>
  </dd>
</dl>

A lone + is the continuation marker (the same one lists and block quotes use): it attaches the following flush-left block to the definition with no indentation.

carve
:: term
: A first paragraph,
+
then a flush-left block joined with +.
html
<dl>
  <dt>term</dt>
  <dd>
    <p>A first paragraph,</p>
    <p>then a flush-left block joined with +.</p>
  </dd>
</dl>

A flush-left line with no blank before it lazily continues the open definition paragraph, exactly as it would inside a list item (and as in djot). A blank line, a new marker, or a block opener ends the definition instead.

carve
:: term
: A definition wrapped
onto the next line.
html
<dl>
  <dt>term</dt>
  <dd>A definition wrapped
onto the next line.</dd>
</dl>

When the definition's sole content is a lone +, it opens a first block: the body is the following flush-left block, with no indentation - the same opener the list form - + provides. Write : \+ for a literal +.

carve
:: term
: +
> the whole definition is this quote
html
<dl>
  <dt>term</dt>
  <dd>
    <blockquote><p>the whole definition is this quote</p></blockquote>
  </dd>
</dl>

The term side is inline-only, like a heading label: it holds inline content (no block content), but it does fold a following plain line into the term with a soft break - a wrapped term line joins the term instead of ending the list. A new marker (:: / : ), a blank line, or a block opener ends the term.

carve
:: A term that
wraps onto the next line
: its definition
html
<dl>
  <dt>A term that
wraps onto the next line</dt>
  <dd>its definition</dd>
</dl>

Comments

4 conformance fixtures

%% starts a line comment and a %%% fence a block comment; neither is rendered.

carve
Visible.

%% this line is a comment

%%%
a hidden
block
%%%

Also visible.
html
<p>Visible.</p>
<p>Also visible.</p>

A trailing %% (preceded by a space or at the start of the line) comments out the rest of the physical line. The visible prefix is kept; the comment is not rendered.

carve
Also visible. %% this tail is a comment
html
<p>Also visible.</p>

A trailing comment works in a heading; it does not affect the generated id.

carve
# Title %% editor note
html
<section id="Title">
  <h1>Title</h1>
</section>

Tooling (carve fmt --stamp) may append a provenance marker - a trailing comment recording the spec version a document was processed under and the engine that wrote it. It is an ordinary comment, so it renders nothing; it is deterministic (no timestamp) and tool-managed (replaced in place, not hand-written).

carve
Hello.

%% carve-version: 0.1; generated-by: carve-js 0.1.0
html
<p>Hello.</p>

Delimited comments

8 conformance fixtures

%% runs to the end of its inline run, so mid-line commenting already works wherever the structure supplies a boundary - a table cell ends at |, link text at ]. Plain prose supplies none. {% … %} is the form for it: it opens at {%, closes at the first %}, and renders nothing.

carve
foo {% bar %} baz
html
<p>foo  baz</p>

It is transparent to the run it sits in, so a comment inside an emphasis span does not break the span.

carve
*bo{% c %}ld* text
html
<p><strong>bold</strong> text</p>

The run may cross a soft line break inside one paragraph - the closer is what ends it, not the line. It never joins two paragraphs across a blank line; %%% is the block form for that.

carve
a {% one
two %} b
html
<p>a  b</p>

An UNTERMINATED opener stays literal text, so a document that opens a comment and never closes it shows the braces rather than swallowing the rest of the paragraph.

carve
a {% oops
html
<p>a {% oops</p>

There is no nesting: the run ends at the FIRST %}, and a {% inside is ordinary comment text.

carve
a {% one {% two %} b
html
<p>a  b</p>

A code span is opaque, exactly as it is for %%.

carve
Run `a {% x %} b` then done.
html
<p>Run <code>a {% x %} b</code> then done.</p>

A backslash on the brace keeps the opener literal.

carve
a \{% not a comment %} b
html
<p>a {% not a comment %} b</p>

Both spellings still work where they already did, and they mean different documents: %% takes the rest of the run, the braced form takes what its closer encloses. The .fmt sidecar beside this pair pins that the writer reproduces the spelling it was given rather than normalizing one into the other.

carve
| a {% hidden %} b | c |
|---|---|
| d %% tail | e |
html
<table>
  <thead>
    <tr><th scope="col">a  b</th><th scope="col">c</th></tr>
  </thead>
  <tbody>
    <tr><td>d</td><td>e</td></tr>
  </tbody>
</table>

Raw blocks

1 conformance fixture

A ```=FORMAT block (a code fence whose info string is =FORMAT) passes its content through verbatim when FORMAT matches the output; other formats are dropped. This is the block parallel of the inline raw {=format} attribute.

carve
```=html
<custom-el>Verbatim HTML</custom-el>
```
html
<custom-el>Verbatim HTML</custom-el>

Hard line breaks

1 conformance fixture

A backslash at the end of a line forces a <br>.

carve
line one\
line two
html
<p>line one<br>
line two</p>

Non-breaking space

2 conformance fixtures

A backslash before a space produces a non-breaking space.

carve
10\ kg
html
<p>10&nbsp;kg</p>

A non-breaking space counts as whitespace for smart-quote flanking, so a quote that follows one opens (exactly as it would after an ordinary space).

carve
say\ 'twas a fine\ "day"
html
<p>say&nbsp;‘twas a fine&nbsp;“day”</p>

Raw inline

1 conformance fixture

A verbatim span tagged {=format} passes through when the format matches the output; otherwise it is dropped.

carve
Use `<br>`{=html} to break, and `\foo`{=latex} is dropped.
html
<p>Use <br> to break, and  is dropped.</p>

Ordered list start and delimiter

2 conformance fixtures

An ordered list that begins above 1 emits start; the ) delimiter is accepted (and a delimiter change starts a new list).

carve
3. third
4. fourth
html
<ol start="3">
  <li>third</li>
  <li>fourth</li>
</ol>
carve
1) one
2) two
html
<ol>
  <li>one</li>
  <li>two</li>
</ol>

Ordered list dialects

2 conformance fixtures

Alphabetic (a./A.) and roman (i./I.) markers set the <ol type>; the first item fixes the dialect and start.

carve
a. apple
b. banana
html
<ol type="a">
  <li>apple</li>
  <li>banana</li>
</ol>
carve
iv. four
v. five
vi. six
html
<ol type="i" start="4">
  <li>four</li>
  <li>five</li>
  <li>six</li>
</ol>

Editorial markup

2 conformance fixtures

CriticMarkup-style review marks: insert, delete, substitute, and an inline comment. The {~ … ~} pair is substitution only when it contains a top-level ~>; without it, it is forced strikethrough (see Forced intraword emphasis). {# … #} is the comment (no collision — # is not an emphasis delimiter).

carve
a {+ins+} {-del-} {~old~>new~} b{# note #}
html
<p>a <ins>ins</ins> <del>del</del> <del>old</del><ins>new</ins> b<span class="critic-comment"> note </span></p>

{=text=} is forced highlight (<mark>), and bare highlight is single-char =. The raw-inline format attribute has its own shape — {=html} (no trailing = before }) on a code span is raw passthrough, distinct from the forced-highlight {=text=}.

carve
=x= and {=y=} both mark.
html
<p><mark>x</mark> and <mark>y</mark> both mark.</p>

Thematic breaks

1 conformance fixture

A line of three or more -, *, or _ is a thematic break.

carve
a

---

b

***

c

___
html
<p>a</p>
<hr>
<p>b</p>
<hr>
<p>c</p>
<hr>

Cross-reference

1 conformance fixture

</#id> links to a heading and fills in its text (here, standalone).

carve
# Getting Started

See </#getting-started>.
html
<section id="Getting-Started">
  <h1>Getting Started</h1>
  <p>See <a href="#Getting-Started">Getting Started</a>.</p>
</section>
4 conformance fixtures

A <url> or <email> in angle brackets becomes a self-titled link; email gets a mailto: scheme.

carve
<https://example.com> and <a@b.com>
html
<p><a href="https://example.com">https://example.com</a> and <a href="mailto:a@b.com">a@b.com</a></p>

A clean URL with only url_chars autolinks normally, query string and all.

carve
<http://a.com/p?x=1>
html
<p><a href="http://a.com/p?x=1">http://a.com/p?x=1</a></p>

A well-formed <url> still autolinks normally.

carve
<http://a.com/>
html
<p><a href="http://a.com/">http://a.com/</a></p>

An email autolink requires a trailing dot and TLD (grammar email_autolink): <a@b.com> becomes a mailto: link, but <a@b> (no dot+TLD) and <x@y:z> (: is not an email character) are not email autolinks and stay literal.

carve
<a@b> <a@b.com> <x@y:z>
html
<p>&lt;a@b&gt; <a href="mailto:a@b.com">a@b.com</a> &lt;x@y:z&gt;</p>

Escapes

1 conformance fixture

A backslash before ASCII punctuation makes it literal.

carve
\*lit\* \[x\] \#h \@u
html
<p>*lit* [x] #h @u</p>

Inline span

1 conformance fixture

A bracketed run followed by an attribute block is a <span>.

carve
A [styled run]{.hl} here.
html
<p>A <span class="hl">styled run</span> here.</p>

Superscript and subscript

1 conformance fixture

Superscript and subscript exist only in the braced forms {^…^} and {,…,} (the same brace-pair family that forces intraword emphasis) — there is no bare ^x^ or ,x, delimiter. The dominant uses (H₂O, mc², 10⁶) are intraword, which only the braced family can express, and a bare comma or caret would collide with plain prose punctuation.

carve
H{,2,}O and E=mc{^2^}
html
<p>H<sub>2</sub>O and E=mc<sup>2</sup></p>

Line blocks

6 conformance fixtures

A ::: | block preserves the author's line layout: each soft line break becomes a hard break (<br>), a blank line starts a new stanza (<p>), and per-line leading whitespace is kept (each leading space serializes as &nbsp; in HTML). It renders as a generic <div class="line-block">. The pipe is the block's type token on the ::: opener - not a per-line prefix - so it is free of the pipe/table ambiguity of the Pandoc per-line | form, with no English keyword.

carve
::: |
Roses are red,
Violets are blue.
:::
html
<div class="line-block">
  <p>Roses are red,<br>
Violets are blue.</p>
</div>

Leading whitespace is preserved; each leading space becomes a non-breaking space so the indentation is visible without extra CSS.

carve
::: |
Roses are red,
  Violets are blue.
:::
html
<div class="line-block">
  <p>Roses are red,<br>
&nbsp;&nbsp;Violets are blue.</p>
</div>

An inner run of two or more spaces is a medial gap - the alignment a caesura or a column of aligned text is made of - and is preserved the same way. A lone inner space stays an ordinary collapsible space, so a long line can still wrap between words.

carve
::: |
Two roads    diverged in a yellow wood,
And looked   down one as far as I could
:::
html
<div class="line-block">
  <p>Two roads&nbsp;&nbsp;&nbsp;&nbsp;diverged in a yellow wood,<br>
And looked&nbsp;&nbsp;&nbsp;down one as far as I could</p>
</div>

A blank line separates stanzas; each stanza is its own paragraph inside the block.

carve
::: |
Stanza one,
still one.

Stanza two.
:::
html
<div class="line-block">
  <p>Stanza one,<br>
still one.</p>
  <p>Stanza two.</p>
</div>

Inline markup inside a line block parses normally; only whitespace and line breaks are special.

carve
::: |
*Bold* and /italic/,
plain line.
:::
html
<div class="line-block">
  <p><strong>Bold</strong> and <em>italic</em>,<br>
plain line.</p>
</div>

::: \ is the other line-break block. It turns soft breaks in direct paragraph children into hard breaks and does nothing else - it does not preserve leading whitespace and does not affect nested blocks. That makes it the one to reach for when the goal is only to show the breaks you typed; ::: | is for the narrower case where the leading whitespace is itself content - verse, addresses, ASCII alignment.

carve
::: \
one
two
:::
html
<div class="hardbreaks">
  <p>one<br>
two</p>
</div>

Admonitions

8 conformance fixtures
carve
::: note
Heads up — this is important.
:::
html
<aside class="admonition note" aria-label="Note">
  <p>Heads up — this is important.</p>
</aside>

Carve renders ::: blocks by a two-tier rule (PART 9 §12). The eight canonical types — note, tip, warning, danger, info, success, example, quote — render as <aside class="admonition {type}">. Any other identifier (hint, tabs, mermaid, details, …) renders as a generic <div class="{type}">, the fenced-div primitive the block-extension mechanism builds on. A quoted title after the type becomes a <p class="admonition-title"> in either tier; the quotes are stripped and never folded into the class.

Recognized ::: type words

A ::: name opener's behavior keys off the type word (not a class). Only these words are recognized by core; every other word is an ordinary generic <div class="{word}"> that an extension may give meaning to.

Type wordRenders asSpecial behavior
note tip warning danger info success example quote<aside class="admonition {type}">Admonition (PART 9 §12); optional quoted title → <p class="admonition-title">
| (pipe)<div class="line-block">Line block - preserves the author's per-line layout / soft breaks (PART 9 §23). The type token is the pipe itself, not a word.
(any other word)<div class="{word}">None in core, a generic fenced div; meaning supplied by a Tier-2 or Tier-3 extension (e.g. tabs, code-group, mermaid).

Because the behavior keys to the bare type word, give a purely presentational container a class on an attribute line before the opener ({.mybox} then :::) so you never collide with a recognized type word. The ::: fence takes no inline attributes (strict djot), so an inline ::: {.mybox} is a paragraph, not a div.

A quoted title on a canonical type renders inside the <aside>:

carve
::: tip "Pro Tip"
Save early, save often.
:::
html
<aside class="admonition tip" aria-labelledby="adm-1">
  <p class="admonition-title" id="adm-1">Pro Tip</p>
  <p>Save early, save often.</p>
</aside>

A custom type renders as a generic <div> with the literal type as its class.

carve
::: hint "Heads up"
Custom call-out.
:::
html
<div class="hint">
  <p class="admonition-title">Heads up</p>
  <p>Custom call-out.</p>
</div>

A [label] after the type (and after any quoted header) is a grouping identifier — the same [label] token a code fence takes. Core ignores it on a standalone block; a group extension (e.g. tabs) uses it as the tab name. It is the canonical replacement for the older tabs {label="…"} / inner-heading convention (both stay supported, deprecated). The selected default-tab marker is not a label — it stays a boolean attribute on the preceding {…} line. Title and label never trade places: under a group extension the quoted header stays inside the panel as its admonition-title line while the [label] moves out to the tab button (and the standalone div-label caption disappears); a header is never used as the tab name.

carve
::: tip "Pro Tip" [Build]
Save early, save often.
:::
html
<aside class="admonition tip" aria-labelledby="adm-1">
  <p class="admonition-title" id="adm-1">Pro Tip</p>
  <p class="div-label">Build</p>
  <p>Save early, save often.</p>
</aside>

The title is ordinary inline content - emphasis, code, and the other inline forms work inside it (unlike a code-fence header, which targets an HTML attribute and stays literal):

carve
::: note "Install *now* via `npm`"
Body.
:::
html
<aside class="admonition note" aria-labelledby="adm-1">
  <p class="admonition-title" id="adm-1">Install <strong>now</strong> via <code>npm</code></p>
  <p>Body.</p>
</aside>

A typeless generic div may carry a label too (a tab member with no semantic type); core still renders a plain <div>.

carve
::: [First]
First panel.
:::
html
<div>
  <p class="div-label">First</p>
  <p>First panel.</p>
</div>
carve
::: warning
Mind the gap.
:::
html
<aside class="admonition warning" aria-label="Warning">
  <p>Mind the gap.</p>
</aside>

An admonition may contain multiple block-level children, including lists and code blocks.

carve
::: tip
Quick steps:

- read the docs
- run the demo
:::
html
<aside class="admonition tip" aria-label="Tip">
  <p>Quick steps:</p>
  <ul>
    <li>read the docs</li>
    <li>run the demo</li>
  </ul>
</aside>

Abbreviations

1 conformance fixture
carve
The HTML spec is essential reading.

*[HTML]: HyperText Markup Language
html
<p>The <abbr title="HyperText Markup Language">HTML</abbr> spec is essential reading.</p>

Mentions and tags

1 conformance fixture
carve
Hey @alice, see #release-1.0.
html
<p>Hey <span class="mention"><strong>@alice</strong></span>, see <span class="tag"><strong>#release-1.0</strong></span>.</p>

Symbols

2 conformance fixtures

:name: is a symbol: a generic named inline placeholder with no built-in semantics. The parser records only the name; resolution is processor configuration, in precedence order: a registered inline-renderer extension handler for symbol nodes, else the renderer symbols map (name → replacement, emitted raw in the target format — processor configuration is trusted, the same class as the renderers map), else the literal text :name:. Emoji substitution is the common use, not a language feature. :type[…] is still an extension and is tried first.

The name starts with a letter, a digit, + or - and continues with word characters, + or -, so the reaction shortcodes :+1: and :-1: parse. It may not start with _: :_x_: would otherwise steal from underline, so :_x: stays literal text. Like mentions and tags, a symbol only opens at the start of content or after a non-word character: a:b:c and 10:30: stay literal text, while (:tada:) is a symbol. (Djot's symbols open intraword; Carve's boundary rule deliberately does not — see the djot divergence notes.)

carve
Great :rocket: and :widget[Ctrl] is an extension.
html
<p>Great :rocket: and <span class="ext-widget">Ctrl</span> is an extension.</p>

A trailing attribute block attaches to the symbol; in HTML output attributes force a <span> wrapper around the resolved (or literal) output so they have an element to land on. Without attributes no wrapper is emitted.

carve
Launch :rocket:{.big} now.
html
<p>Launch <span class="big">:rocket:</span> now.</p>

Inline extensions

7 conformance fixtures

The :name[…] syntax is core; the HANDLERS are not. Core registers none of them, so every name — including the semantic ones — falls back to a generic <span class="ext-NAME"> until an extension claims it. The seven semantic names are the SemanticSpan extension's, and the :name[…] spelling for them is soft-deprecated there: write the span attribute instead (PART 9 §9, §10).

carve
Press :kbd[Ctrl+C] to copy.
html
<p>Press <span class="ext-kbd">Ctrl+C</span> to copy.</p>

The same keystroke as core writes it — a semantic span attribute, which needs no extension:

carve
Press [Ctrl+C]{kbd} to copy.
html
<p>Press <kbd>Ctrl+C</kbd> to copy.</p>

An unrecognized extension name falls back to the same generic span.

carve
:foo[bar]
html
<p><span class="ext-foo">bar</span></p>

An authored {.class} on a generic inline extension merges into the single class attribute — the structural ext-NAME class comes first, then the authored classes. There is never a second class attribute.

carve
:foo[a]{.cls}
html
<p><span class="ext-foo cls">a</span></p>

Core reserves three of the seven names as SPAN ATTRIBUTES - abbr and time, which carry data, and kbd, which every comparable system ships. A value on abbr becomes title and a value on time becomes datetime; several names nest in the fixed order abbr, time, kbd.

carve
[HTML]{abbr="HyperText Markup Language"} [Noon]{time="12:00"} [Tab]{kbd}
html
<p><abbr title="HyperText Markup Language">HTML</abbr> <time datetime="12:00">Noon</time> <kbd>Tab</kbd></p>

:cite[text] is a work title or source, not a bibliographic [@key] citation. :abbr[text]{title="…"} is independent of automatic abbreviation definitions. Names outside the fixed registry retain the readable generic fallback.

On that fallback the structural ext-NAME class is not written ahead of everything: authored attributes keep their SOURCE order (PART 10 §1), and the base class merges into the class slot at the position the author put their own class. An id written before a class therefore stays before it.

carve
:widget[x]{#i .c k=v}
html
<p><span id="i" class="ext-widget c" k="v">x</span></p>

With no class of their own there is no authored position to respect, so the base class leads.

carve
:widget[x]{#i k=v}
html
<p><span class="ext-widget" id="i" k="v">x</span></p>

Numbered cross-references

7 conformance fixtures

A # in a caption is a number placeholder: the label is the text before it, the number is injected in its place, and </#id> to the element resolves to "label + number".

carve
{#fig-sun}
![A sunset](sun.jpg)
^ Figure #: A sunset
html
<figure id="fig-sun">
  <img src="sun.jpg" alt="A sunset">
  <figcaption>Figure 1: A sunset</figcaption>
</figure>

Numbers run per label, in document order.

carve
![one](a.jpg)
^ Figure #: one

![two](b.jpg)
^ Figure #: two
html
<figure>
  <img src="a.jpg" alt="one">
  <figcaption>Figure 1: one</figcaption>
</figure>
<figure>
  <img src="b.jpg" alt="two">
  <figcaption>Figure 2: two</figcaption>
</figure>

A </#id> to a numbered caption fills its text with the label and number.

carve
{#fig-sun}
![A sunset](sun.jpg)
^ Figure #: A sunset

See </#fig-sun> for the colors.
html
<figure id="fig-sun">
  <img src="sun.jpg" alt="A sunset">
  <figcaption>Figure 1: A sunset</figcaption>
</figure>
<p>See <a href="#fig-sun">Figure 1</a> for the colors.</p>

Tables use the same placeholder; the number lands in the <caption>.

carve
{#tbl-r}
|= Item |= Qty |
| Apple | 3 |
^ Table #: Stock

See </#tbl-r>.
html
<table id="tbl-r">
  <caption>Table 1: Stock</caption>
  <thead>
    <tr><th scope="col">Item</th><th scope="col">Qty</th></tr>
  </thead>
  <tbody>
    <tr><td>Apple</td><td>3</td></tr>
  </tbody>
</table>
<p>See <a href="#tbl-r">Table 1</a>.</p>

Labels bucket independently, so other languages number on their own.

carve
![a](a.jpg)
^ Abbildung #: erstes

![b](b.jpg)
^ Figure #: first
html
<figure>
  <img src="a.jpg" alt="a">
  <figcaption>Abbildung 1: erstes</figcaption>
</figure>
<figure>
  <img src="b.jpg" alt="b">
  <figcaption>Figure 1: first</figcaption>
</figure>

A caption after a fenced code block makes it a numbered listing: the block is wrapped in a <figure>, and </#id> resolves to "Listing N" on the same per-label counter as figures and tables.

carve
{#lst-greet}
```python
def greet():
    return 1
```
^ Listing #: a greeting

See </#lst-greet>.
html
<figure id="lst-greet">
  <pre><code class="language-python">def greet():
    return 1
</code></pre>
  <figcaption>Listing 1: a greeting</figcaption>
</figure>
<p>See <a href="#lst-greet">Listing 1</a>.</p>

A caption after a standalone display-math block makes it a numbered equation: the math is wrapped in a <figure>, and </#id> resolves to "Equation N" on its own per-label counter. Only a block whose sole content is the display-math span qualifies; inline math, or display math with trailing prose, is untouched.

carve
{#eq-emc}
$$`E = mc^2`
^ Equation #: mass-energy

See </#eq-emc>.
html
<figure id="eq-emc">
  <p><span class="math display" role="math">\[E = mc^2\]</span></p>
  <figcaption>Equation 1: mass-energy</figcaption>
</figure>
<p>See <a href="#eq-emc">Equation 1</a>.</p>

Released under the MIT License.