Markfive is a lightweight markup language that compiles to semantic HTML. It
looks similar to Markdown, but it covers many more HTML elements (<dfn>,
<kbd>, <samp>, <cite>, <mark>, <time>, <ruby>, …), has
first-class footnotes, tables with merged cells, MathML output and
language-aware typography.
Paragraphs are separated by blank lines. Unlike Markdown, every line break
inside a paragraph is kept (rendered as <br>):
First line of a paragraph.
Second line of the same paragraph.
Another paragraph.
Inside a line, {/} inserts a line break (<br>) and {/?} a word-break
opportunity (<wbr>).
A thematic break (<hr>) is a * * * line followed by a blank line.
A heading is a line underlined with at least three characters. The underline character sets the level:
Document title
**************
Chapter
=======
Subchapter
----------
Section
.......
| Underline | Element |
|---|---|
*** |
<h1> |
=== |
<h2> |
--- |
<h3> |
... |
<h4> |
A heading must be followed by a blank line. A single line placed between the
heading and the blank line becomes a subtitle and the pair is wrapped in
<hgroup>:
Chapter 2
=========
Subtitle
Every heading of level 2 or lower opens a <section> that lasts until the
next heading of the same or higher level. Each heading gets an id generated
from its text (Chapter 2 → chapter_2); duplicates are made unique. Use
--heading-shift=N to render all headings N levels lower.
HTML attributes are written in braces:
{#id .class1.class2 lang=en title="Quoted value" data-foo='bar' hidden}
#namesets theid,.nameadds classes (several can be chained:.a.b),name=valuesets an attribute (quote the value if it contains spaces),- a bare
namesets an empty attribute.
Block attributes go on their own line directly before the block:
{style="color: orange"}
A paragraph with a style.
{.compact}
- First item
- Second item
{#custom-id}
Heading with a custom id
------------------------
An attributes line at the very top of the file, followed by a blank line,
applies to the whole document (e.g. {lang=fr} sets the language used for
typography).
Block markers accept attributes right after the marker: ""{.note},
``{.language-js}, ??{.box}, @main{#content}, -{.done} item,
1.{.done} item, |{.total} cell.
Inline attributes go right after the closing marker:
*word*{lang=en}, [link](url){target=_blank}, ?[text]?{.highlight}.
| Markfive | HTML |
|---|---|
*strong* |
<strong> |
~emphasis~ |
<em> |
#bold# |
<b> |
/italic/ |
<i> |
__underline__ |
<u> |
--strikeout-- |
<s> |
=highlight= |
<mark> |
''Title'' |
<cite> |
:term: |
<dfn> |
:term:(Title) |
<dfn title="Title"> |
`code` |
<code> |
>command> |
<kbd> |
<output< |
<samp> |
$x$ |
<var> (or MathML, see Math) |
x^2^ |
<sup> |
H_2_O |
<sub> |
?[text]? |
<span> |
[[Enter]] |
key (<kbd class="mf-key">) |
[[Ctrl]+[C]] |
key combination |
[|OK|] |
button (<kbd class="mf-button"><samp>) |
[|File|Save|] |
menu path |
Markers only work at word boundaries: the opening marker must not be preceded
by a letter or digit and the closing marker must not be followed by one, so
parag~raph stays literal. To format part of a word, wrap the content in
brackets: #[J]#ava#[S]#cript. Superscript and subscript are the exception –
they work inside words (km^2^), but content containing spaces must be
bracketed: sli^[ce of text]^.
*strong*, ~emphasis~, =highlight= and ?[span]? can be nested in
themselves: ~outer ~inner~ outer~.
Other inline HTML elements are written as @element[content]:
@abbr[HTML]{title="HyperText Markup Language"}
@small[fine print]
@del[removed] @ins[added]
@bdi[user name]
@bdo[reversed](rtl)
@ruby[漢 @rt[kan]]
@time[today](2025-07-30)
@data[five](5)
Supported elements are abbr, bdi, bdo, data, del, ins, rt,
ruby, small and time. For bdo, time and data the value in
parentheses becomes the dir, datetime and value attribute respectively.
[link text](https://example.com)
[link text](https://example.com){target=_blank}
&[alt text](image.png){.class}
If the alt text of an image contains formatting, the image is rendered as
<object role="img"> with the formatted content as its fallback.
Footnotes are written inline and collected at the end of the document:
Some claim[*][Source of the claim.].
The number of asterisks selects an independent series of notes: [*] is
numbered 1, 2, 3, [**] uses a, b, c and [***] uses A, B, C.
A note can be given an id and then referenced again:
First mention[*][The note.]{#src}. Second mention[*](#src).
- Unordered item
- Another item
- Nested item (indented with a tab)
1. Ordered item
2. Another item
5. Item with value 5
Numbers that don't continue the sequence are kept as the value attribute. A
blank line between two items at the same level starts a new list.
To put several blocks in one item, leave the marker empty and indent the content:
1. Item 1
2.
First paragraph of item 2.
Second paragraph of item 2.
3. Item 3
Terms start with : and descriptions with =:
: Apartment
= An execution context grouping threads with COM objects.
: Flat
= A deflated tire.
Alternatively, a line ending with a colon followed by an indented line forms a term-description pair:
Fuzzy logic:
gradual transition between "perfect falsity" and "perfect truth"
""
A quotation. It can contain
several paragraphs and other blocks.
"" A one-line quotation.
A code block (<pre><code>) starts with `` and its content is
indented. Whitespace and indentation inside it are preserved:
``
if n < 0:
raise ValueError()
`` print('A one-line code block')
Lines starting with > form a keyboard input block (<pre><kbd>) and lines
starting with < a sample output block (<pre><samp>):
> npm install
> npm start
< added 42 packages in 2s
?? creates a <div> containing the indented blocks:
??{.box}
A paragraph in a div.
??{.inner}
A nested div.
??{.one-liner} A one-line div.
??{.empty}
@header, @main, @footer, @aside, @address, @pre, @del and
@ins create the corresponding HTML element around their indented content.
Inside @pre whitespace is preserved.
@main
Main content.
@pre
Pre formatted
text
In a simple table each line is a row; ! starts a header cell and | a data
cell:
! Name ! Position
| Chao | Intern
| Nguyễn | Worker
Each > after the cell marker makes the cell span one more column. A ^
after the marker merges the cell with the one above it (its content, if any,
is appended to that cell):
! First name ! Last name ! Paid
| Bob | Smith | 600
|^ |^ p.v. | 400
|> Total | 1000
In a complex table rows are separated with lines starting with |- (any
further - and | characters are ignored, so they can be used to draw the
grid) and every cell can be written on its own line:
|-------|--------|-----
! Title ! Author ! Year
|-------|--------|-----
| The Title
| Author
| 2015
Cell attributes go right after the cell marker: |{.highlight} 2025.
Math is rendered to MathML with Temml. A display
formula is a line starting with $$ (the closing $$ is optional); inline
math is enclosed in single dollars:
$$ E = \frac{mc^2}{\sqrt{1-\frac{v^2}{c^2}}} $$
Here, $E$ is energy. Similar equation is $E=mc^2$.
An inline formula consisting only of letters ($x$) is rendered as a simple
<var>.
LaTeX syntax is supported, extended with a few shortcuts:
a / bis a fraction; parentheses around the numerator or denominator are dropped:(-b \pm \sqrt{b^2 - 4ac}) / 2a,√{x}and∛{x}are square and cube roots,- braces after a set operator are literal:
S ⊂ {a, b, c}.
Comments are not rendered:
// A one-line comment.
//
A commented-out block.
- including a list
{{name}} is replaced with the value passed on the command line as
--name=value. Undefined variables are replaced with an empty string.
Unless --no-typography is given, the following replacements are made outside
of code:
| Input | Output |
|---|---|
"text", 'text' |
quotation marks of the current language |
... |
… |
--- |
— (em dash) |
3--5 |
3–5 (en dash) |
-15 |
−15 (minus sign) |
5', 12'' |
5′, 12″ (primes) |
1600 x 900 |
1600 × 900 |
<<text>> |
«text» |
<text> |
‹text› |
[<u, v>] |
⟨u, v⟩ (angle brackets) |
Angle brackets take precedence over <samp< and >kbd> markers, and their
content can be formatted: [<*A*>]. A link whose text starts with < must
escape it: [\<b>](https://example.com).
The language is taken from the nearest lang attribute (English by default),
e.g. "text" becomes “text” in English, „text” with {lang=pl} and
„text“ with {lang=de}. For
French, narrow no-break spaces are added before ?, !, : and inside guillemets.
A backslash before a marker makes it literal: \*not strong\*. A backslash
at the beginning of a line prevents it from being interpreted as a block
marker:
\- Not a list item
\* * *
Use \\ for a literal backslash followed by a working marker.
Install dependencies (this also builds the project into lib/):
npm install
Make the markfive command available on PATH:
npm link
The command points to lib/cli.js in the working copy, so every rebuild takes
effect immediately. When using nvm, the link belongs to the current Node
version – run npm link again after switching versions. To remove the
command:
npm unlink -g markfive
Building:
npm run build
Develop mode (faster builds, watching for changes):
npm run dev
Running test:
markfive test/test.mf
Writing test to HTML file:
markfive test/test.mf > test/test.part.html
markfive --preview test/test.mf > test/test.html