Extended Markdown parser for rich, structured web pages — works in the browser and Node.js.
Demo & Docs → · Showcase → · Try on StackBlitz →
lobster.js takes a Markdown file with its own extended syntax and turns a near-empty HTML page into a fully structured document — no build step, no framework.
It provides document structure only; appearance is entirely up to CSS via predictable lbs-* class names.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="your-style.css" />
<!-- bring your own CSS -->
</head>
<body>
<div id="content"></div>
<script type="module">
import { loadMarkdown } from "./lobster.js";
loadMarkdown("./content.md", document.getElementById("content"));
</script>
</body>
</html>lobster.js outputs semantic HTML with lbs-* class names. No stylesheet is bundled in the package — styling is entirely up to you. Ready-to-use CSS themes are provided in docs/themes/ for quick starts and inspiration.
- All standard Markdown — headings, paragraphs, bold, italic, strikethrough, code, blockquotes, lists, tables, links, images
- Extended syntax unique to lobster.js:
:::header/:::footer— semantic page header and footer regions:::details— native collapsible<details>/<summary>blocks:::warp+[~id]— define content once, place it anywhere (great for multi-column layouts)- Silent tables (
~ | … |) — borderless layout grids - Table cell merging — horizontal (
\|) and vertical (\---) colspan/rowspan - Image sizing —
 - Heading anchors —
## My Section {#my-section}adds anidattribute for deep linking - Footnotes — reference (
[^id]) and inline (^[text])
- CSS-first styling — all elements get
lbs-*class names; bring your own stylesheet - Multi-file loading — pass an array of paths to
loadMarkdownto merge multiple Markdown files into one page; all warp/link/footnote definitions are shared across files - Platform-agnostic core —
src/core/has zero DOM/browser dependencies; portable to Node.js, Deno, and potentially native mobile - TypeScript — full type definitions included
npm install @hacknock/lobster
# or
pnpm add @hacknock/lobsterimport { toHTML } from "@hacknock/lobster";
const html = toHTML("# Hello\n\nThis is **lobster**.");The latest build is available via GitHub Pages:
<script type="module">
import { loadMarkdown } from "https://hacknock.github.io/lobsterjs/lobster.js";
loadMarkdown("./content.md", document.getElementById("content"));
</script>Or download lobster.js from the releases page and host it yourself.
Convenience one-liner. Parses and renders Markdown to an HTML string.
import { toHTML } from "@hacknock/lobster";
const html = toHTML("# Hello");Parses Markdown into an AST. Useful for inspecting or transforming the document tree before rendering.
import { parseDocument } from "@hacknock/lobster";
const doc = parseDocument(markdown);
console.log(doc.body); // BlockNode[]Converts a Document AST into an HTML string.
import { parseDocument, renderDocument } from "@hacknock/lobster";
const html = renderDocument(parseDocument(markdown));Fetches one or more Markdown files and renders them into the given DOM element (defaults to document.body). Browser only.
When an array is passed, all files are fetched in parallel, concatenated with a blank line, and parsed as a single document — so warp/link/footnote definitions are shared across all files.
import { loadMarkdown } from "@hacknock/lobster";
// Single file
await loadMarkdown("./content.md", document.getElementById("content"));
// Multiple files — fetched in parallel, merged before parsing
await loadMarkdown(
["./shared.md", "./content.md"],
document.getElementById("content")
);Standard Markdown works as you would expect. The sections below highlight lobster.js-specific extensions.
:::header
# My Site
:::
:::footer
© 2025 My Site
:::
:::details Click to expand
Hidden content goes here.
:::
~ | left | right |
~ | :--- | :--- |
~ | [~col-left] | [~col-right] |
:::warp col-left
Left column content
:::
:::warp col-right
Right column content
:::


Lobster[^1] is great. So is inline^[No definition needed] footnotes.
[^1]: A crustacean — or a Markdown parser.
| A | B \| | ← B and the next cell merge horizontally
| \--- | C | ← first cell merges with the one above
For the full syntax reference see markdowns/spec.md (English) or markdowns/spec-ja.md (日本語).
Every rendered element carries a lbs-* class name so any stylesheet can target it.
For the complete HTML output reference, minimum viable stylesheet, dark mode tips, and
an AI prompt template, see markdowns/styling.md.
Quick reference:
| Class | Element |
|---|---|
lbs-heading-1 … lbs-heading-6 |
Headings (<h1>…<h6>) |
lbs-paragraph |
Paragraph |
lbs-emphasis / lbs-strong / lbs-strikethrough |
Inline decorations |
lbs-code-span / lbs-code-block / lbs-code-filename |
Code |
lbs-blockquote |
Blockquote |
lbs-ul / lbs-ol / lbs-list-item / lbs-checkbox |
Lists |
lbs-table / lbs-table-silent |
Tables |
lbs-header / lbs-footer |
Page regions |
lbs-details / lbs-summary |
Collapsible |
lbs-footnote-ref / lbs-footnotes / lbs-footnote-item |
Footnotes |
The repository ships several ready-to-use stylesheets you can copy and adapt:
| File | Style |
|---|---|
docs/style.css |
Default — clean, minimal, light |
docs/themes/pop.css |
Pop — bright, rounded, playful (coral/pink) |
docs/themes/pastel.css |
Pastel — soft, dreamy, gentle (lavender/mint) |
docs/themes/formal.css |
Formal — strict, professional, serif (navy) |
docs/themes/engineer.css |
Engineer — dark, terminal-like, dev-tool feel |
You can switch between them on the demo page using the theme selector in the page header.
lobster.js has no built-in highlighter, but fenced code blocks with a language tag emit a language-* class on the <code> element — the convention used by Prism.js, highlight.js, and most other highlighters.
```js
console.log("hello");
```Rendered output:
<div class="lbs-code-block">
<pre data-language="js"><code class="language-js">console.log("hello");</code></pre>
</div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs/themes/prism.min.css" />
<script src="https://cdn.jsdelivr.net/npm/prismjs/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs/plugins/autoloader/prism-autoloader.min.js"></script>import { loadMarkdown } from "./lobster.js";
loadMarkdown("./content.md", document.getElementById("content")).then(() => Prism.highlightAll());import { loadMarkdown } from "./lobster.js";
import hljs from "highlight.js";
loadMarkdown("./content.md", document.getElementById("content")).then(() => hljs.highlightAll());lobster.js renders fenced ```mermaid blocks as <code class="language-mermaid">. After loadMarkdown() resolves, replace those elements with Mermaid's expected <div class="mermaid"> and call mermaid.run().
```mermaid
flowchart LR
A[Plan] --> B[Delay] --> C[Cancel] --> D[Apologize] --> A
```<script type="module">
import { loadMarkdown } from "https://hacknock.github.io/lobsterjs/lobster.js";
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
mermaid.initialize({ startOnLoad: false });
await loadMarkdown("./content.md", document.getElementById("content"));
document.querySelectorAll("code.language-mermaid").forEach(code => {
const wrapper = code.closest(".lbs-code-block");
const div = document.createElement("div");
div.className = "mermaid";
div.textContent = code.textContent;
(wrapper ?? code).replaceWith(div);
});
await mermaid.run();
</script>If you use Claude Code, this repository ships two slash commands in .claude/commands/:
| Command | What it does |
|---|---|
/lobster-init |
Scaffolds index.html + content.md. Pass a description to generate real content, or omit it for a bare skeleton. |
/lobster-css |
Generates a CSS stylesheet targeting lbs-* class names. Describe the design you want. |
Clone the repo and the commands are immediately available in any Claude Code session opened in this directory.
| Tool | Version |
|---|---|
| mise | 2026.2.19+ |
| pnpm | 10.30.1 |
| Node.js | 24.13.1 |
# Install mise
brew install mise
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc
# Install pnpm and Node.js (versions defined in mise.toml)
mise install
# Install dependencies
pnpm installpnpm test # Run tests (Vitest)
pnpm test:watch # Watch mode
pnpm build # Bundle to dist/
pnpm build:docs # Copy bundle to docs/ for GitHub Pagessrc/
├── core/ # Platform-agnostic parser (no DOM dependency)
│ ├── types.ts # All AST type definitions
│ ├── block-parser.ts # Block-level parser
│ ├── inline-parser.ts # Inline-level parser
│ └── index.ts
├── renderer/html/
│ ├── renderer.ts # AST → HTML string
│ └── dom.ts # DOM helpers (browser only)
├── index.ts # Browser entry point (full API)
└── index.node.ts # Node.js entry point (pure functions only)
tests/ # Vitest test suites
markdowns/ # Specification documents
docs/ # GitHub Pages demo site
Issues and pull requests are welcome.
- Fork the repository
- Create a feature branch:
git checkout -b feat/my-feature - Add tests for any new behaviour
- Run
pnpm testand make sure all tests pass - Open a pull request
MIT © 2022 Hacknock