See also: spec-ja.md (Japanese)
lobster.js parses an extended Markdown dialect and renders it as structured HTML.
It provides document structure only — visual presentation is delegated to CSS via predictable lbs-* class names.
- Parsing is left-to-right, first-match within each line.
- Trailing spaces on every line are trimmed before parsing.
- HTML tags in Markdown source are treated as plain text (escaped).
- When the same definition appears multiple times, the last one wins (except where noted).
The parser processes elements in the following priority:
Header > Footer >>> Details =>
Blockquote => List > Heading > Horizontal Rule > Code Block > Silent Table > Table >
Image > Inline Footnote > Footnote Ref > Warp Ref > Inline Link > Link >
Code Span > Strong > Emphasis > Strikethrough
Block-level elements (:::header, :::footer, :::warp) are extracted from the document in a first pass before the body is parsed.
# H1
## H2
### H3
#### H4
##### H5
###### H6
#count determines level (1–6). A minimum of one space between#and text is required.- Trailing
#characters preceded by a space are ignored:# Title ##→ H1 "Title". - Text on the same line (including other Markdown) forms the heading content.
Anchor ID (optional):
Append {#id} at the end of a heading line to set an explicit id attribute:
## My Section {#my-section}
- The
{#id}suffix is stripped from the visible text and emitted as an HTMLid. - Useful for deep-linking and in-page navigation (
href="#my-section").
HTML output:
<h1 class="lbs-heading-1">…</h1>
<!-- through -->
<h6 class="lbs-heading-6">…</h6>
<!-- with anchor id -->
<h2 class="lbs-heading-2" id="my-section">My Section</h2>One or more blank lines separate paragraphs.
HTML output:
<p class="lbs-paragraph">…</p>A newline within a paragraph produces a <br>. The final newline of a paragraph is not converted.
Note: trailing two-space line endings are not treated specially; a bare newline is enough.
Three or more - or * characters on a line, with optional spaces between them:
---
- - -
***
* * *
Mixed characters (--**) are not recognized.
HTML output:
<hr class="lbs-hr">Fenced with ``` or ~~~ (at least 3 characters). Opening fence may include a language identifier and an optional filename:
```js:filename.js
console.log("Hello");
```
- Language: any word characters after the fence marker.
- Filename: follows
:after the language; may contain any character except`and:. - Language may be omitted while filename is still specified:
````:filename. - Content is treated as plain text (no Markdown parsing).
HTML output:
<div class="lbs-code-block">
<div class="lbs-code-filename">filename.js</div> <!-- omitted if no filename -->
<pre data-language="js" data-filename="filename.js"><code>…</code></pre>
</div>Lines starting with >. Consecutive > lines (or continuation lines) without a blank line form one blockquote block. Nesting is achieved by stacking >:
> Outer
> > Inner
All standard Markdown is valid inside blockquotes.
HTML output:
<blockquote class="lbs-blockquote">…</blockquote>At least one space must separate the marker from the item text.
Markers: -, *, +
- Item
- Item
- Nested item
Items with more indentation than the parent become sub-items. The nesting depth is tracked per list node.
HTML output:
<ul class="lbs-ul lbs-ul-depth-0">
<li class="lbs-list-item">…</li>
</ul>Marker: N. where N is any integer.
1. First
2. Second
1. Nested
N\. (backslash-escaped dot) is treated as plain text, not a list marker. The start attribute reflects the first item's number.
HTML output:
<ol class="lbs-ol lbs-ol-depth-0" start="1">
<li class="lbs-list-item">…</li>
</ol>Applies to both bullet and ordered lists:
- [ ] Unchecked
- [x] Checked
- [X] Also checked
HTML output:
<li class="lbs-list-item">
<input type="checkbox" class="lbs-checkbox" disabled> …
<!-- "checked" attribute added for [x]/[X] -->
</li>A table requires at minimum a header row and an alignment row.
Leading and trailing | are optional.
| Name | Age |
| ----- | --- |
| Alice | 30 |
| Cell syntax | Meaning |
|---|---|
--- |
Default (inherits CSS text-align) |
:--- |
Left |
:---: |
Center |
---: |
Right |
Spaces are allowed before/after cell content and alignment markers. No spaces within --- or : sequences.
Horizontal merge — the \| pattern is a merge marker: the | separator between two cells, when immediately preceded by \, causes the left cell to span two columns. Place the content in the left cell and leave the right cell empty:
| A | B \| |
Constraint: When both sides of
\|contain content (e.g.| A \| B |), the right-side content (B) is silently dropped. Only the left-side content (A) is kept and given colspan=2. Write| A \| |to clearly express a merge.
Vertical merge — use \--- (one or more dashes) in a cell to merge upward:
| A | B |
| \--- | C |
If a row has fewer cells than the header, the missing cells are left empty.
HTML output:
<table class="lbs-table">
<thead><tr><th>…</th></tr></thead>
<tbody><tr><td>…</td></tr></tbody>
</table>Alignment is applied as inline style="text-align:…" on each cell.
Inline elements are parsed left-to-right within block content.
`code`
Use N backticks to include N−1 backticks inside: ``code`` → `code`
When the opening delimiter is two or more backticks, one leading and one trailing space (if both present) are stripped from the content.
HTML output: <code class="lbs-code-span">…</code>
*text* or _text_
- Opening and closing delimiter must be the same character.
- Cannot span newlines.
**text*→ literal*+ emphasis*text*.
HTML output: <span class="lbs-emphasis">…</span>
**text** or __text__
- Opening and closing delimiter must be the same character.
- Cannot span newlines.
***text***→ literal*+ strong**text**+ literal*.
HTML output: <span class="lbs-strong">…</span>
~~text~~
~~~text~~~— only the innermost~~pair is recognized.- Cannot span newlines.
HTML output: <span class="lbs-strikethrough">…</span>
[text](url "title")
- No space allowed between
]and(. - Title is optional; it may be wrapped in
"…",'…', or(…). urlandtextmay be empty.
HTML output: <a href="url" title="title">…</a>
[text][id] ← reference
[id]: url "title" ← definition (anywhere in document)
[text][]— implicit shortcut: the text itself is the id.[text]— if the text matches a definition id, also resolves.- id lookup is case-insensitive.
- Definition
titlemay use"…",'…', or(…). - Multiple
[text][id]references may share one definition (N:1). - Definition lines are not rendered as content.
HTML output: <a href="url" title="title">…</a>

- Follows the same rules as inline links.
!and[must be adjacent (no space).- Size is optional:
=800x600,=800x(width only),=x600(height only).
HTML output:
<img src="url" alt="alt" title="title" width="800" height="600" class="lbs-image">[^id] ← reference (inline)
[^id]: text ← definition (anywhere in document)
- No spaces inside
[^id]or[^id]:. - Multiple references to the same footnote are numbered
[1],[1:1],[1:2], … - Definition lines are not rendered as content.
- Definitions are rendered as a footnote section at the bottom of the document.
HTML output (reference):
<sup class="lbs-footnote-ref"><a href="#lbs-fn-id">[1]</a></sup>HTML output (footnote section):
<section class="lbs-footnotes">
<ol>
<li id="lbs-fn-id" class="lbs-footnote-item">[1] …</li>
</ol>
</section>^[text]
- No space between
^and[. - Behaves identically to a named footnote reference but the definition is written inline.
- Takes priority over named footnote references (left-to-right).
HTML output: same as footnote reference.
All custom blocks (:::header, :::footer, :::details, :::warp) are closed by a ::: line. Leading whitespace before ::: is ignored, so formatter-indented closing markers work correctly.
Recommended style: Place a blank line before the closing
:::. This prevents Markdown formatters (e.g. Prettier) from treating:::as a continuation of the preceding block (such as a list item) and indenting it unexpectedly.
:::header
content
:::
- Only one header per document; subsequent ones overwrite the previous.
- May contain any Markdown except another
:::headeror:::footer. - Rendered before the document body.
HTML output:
<header class="lbs-header">…</header>:::footer
content
:::
Same rules as header. Rendered after the footnote section (if any), at the very end.
HTML output:
<footer class="lbs-footer">…</footer>:::details Summary title
content
:::
- The title follows
:::detailson the same line. - May contain any Markdown except
:::header/:::footer.
HTML output:
<details class="lbs-details">
<summary class="lbs-summary">Summary title</summary>
…
</details>:::warp my-id
content
:::
- Defines a named content block that can be placed elsewhere with
[~my-id]. idmust be unique within the document; duplicate ids cause all content to be rendered as plain text.- Valid id characters: alphanumeric,
-,_. - Warp definitions are not rendered at their definition site; only at reference sites.
Warp reference:
[~my-id]
- Typically used inside silent table cells to create multi-column layouts.
Example:
A silent table requires at least a header row and an alignment row. Warp references can appear in either the header row (Pattern A) or a data row (Pattern B).
Pattern A — warp refs in header row (content rendered in <th>):
~ | [~col-left] | [~col-right] |
~ | :--- | :--- |
:::warp col-left
Left column content
:::
:::warp col-right
Right column content
:::
Pattern B — warp refs in data row (content rendered in <td>, recommended for layouts):
~ | left | right |
~ | :--- | :--- |
~ | [~col-left] | [~col-right] |
:::warp col-left
Left column content
:::
:::warp col-right
Right column content
:::
Prefix every row of a table with ~ to suppress borders:
~ | Col1 | Col2 |
~ | ---- | ---- |
~ | A | B |
HTML output:
<table class="lbs-table lbs-table-silent">…</table>| Class | Element |
|---|---|
lbs-heading-1 … lbs-heading-6 |
Headings (<h1>…<h6>) |
lbs-paragraph |
Body paragraph |
lbs-emphasis |
Italic span |
lbs-strong |
Bold span |
lbs-strikethrough |
Strikethrough span |
lbs-code-span |
Inline code |
lbs-hr |
Horizontal rule |
lbs-code-block |
Code block wrapper <div> |
lbs-code-filename |
Filename label inside code block |
lbs-blockquote |
Blockquote |
lbs-ul |
Unordered list |
lbs-ul-depth-N |
Depth indicator (0 = top level) |
lbs-ol |
Ordered list |
lbs-ol-depth-N |
Depth indicator |
lbs-list-item |
List item |
lbs-checkbox |
Checklist checkbox |
lbs-table |
Table |
lbs-table-silent |
Silent (borderless) table |
lbs-image |
Image |
lbs-header |
Page header |
lbs-footer |
Page footer |
lbs-details |
Details/summary block |
lbs-summary |
Summary element inside details |
lbs-footnote-ref |
Footnote superscript |
lbs-footnotes |
Footnote section |
lbs-footnote-item |
Individual footnote entry |
import { toHTML, parseDocument, renderDocument } from 'lobsterjs';
// Convenience: Markdown → HTML string
const html = toHTML(markdownString);
// Step-by-step (useful for tooling or custom renderers)
const doc = parseDocument(markdownString); // → Document AST
const html = renderDocument(doc); // → HTML string
// Browser: fetch and render a Markdown file into a DOM element
import { loadMarkdown } from 'lobsterjs';
await loadMarkdown('./content.md', document.getElementById('content'));parseDocument returns a Document object:
interface Document {
header?: HeaderContainerNode;
footer?: FooterContainerNode;
body: BlockNode[];
linkDefs: Record<string, LinkDef>;
footnoteDefs: Record<string, InlineNode[]>;
footnoteRefs: string[];
warpDefs: Record<string, WarpDefinitionNode>;
}The full type definitions are in src/core/types.ts.