Render Markdown or piped text to a clean, self-contained HTML page and open it in the browser.
html turns a Markdown file — or anything you pipe to it (tree -d | html, git diff --color | html, cat main.go | html) — into a single offline HTML document with GitHub-style rendering, syntax highlighting, and copy buttons, then opens it in your browser. No external CSS, JS, or fonts: the output is one file you can email, commit, or open on a plane. Non-Markdown input (logs, source, JSON, command output) renders as faithful, syntax-highlighted preformatted text instead of being mangled by the Markdown parser. Results are cached and only re-rendered when the source changes.
# Immutable release (recommended)
go install github.com/dotcommander/html/cmd/html@v0.2.1
# Latest published release
go install github.com/dotcommander/html/cmd/html@latestRequires Go 1.26.3 or newer on macOS, Linux, or Windows. The directory selected
by GOBIN (or the first GOPATH entry plus /bin) must be on PATH.
html README.md # render + open in your browser (prints the cache path)
html -n README.md # render only, don't open (-n / --no-open)
tree -d | html # pipe any command output — auto-detectedhtml README.md # Markdown file → GitHub-style page, opened in the browser
tree -d | html # pipe stdin: auto-detected as Markdown or plain text
git diff --color | html # ANSI colors preserved as styled spans — diffs stay colored
git diff --color | html --frame # wrap it in a faux terminal window — a share-ready "screenshot"
cat main.go | html # plain code is auto syntax-highlighted (language detected)
html data.json # files highlight by extension (.go / .json / .py / …)Generated CSS and JavaScript are always embedded. In trusted Markdown mode, supported local images up to 10 MiB each are embedded until the document reaches the 32 MiB image budget; remote, missing, unsupported, oversized, and over-budget images remain external references. Repeated references count toward the budget. For trusted Markdown files, the CLI reports each distinct non-embedded image on stderr with a stable reason code, without changing the rendered document or cache path written to stdout. Plain text and stdin have no local-image base directory.
| Flag | Effect |
|---|---|
-n, --no-open |
render only; print the cache path without opening the browser |
-f, --force |
rebuild even if the cached HTML is fresh |
-o, --output <path> |
write the HTML to a stable path (- = stdout) |
-p, --plain |
force preformatted plain text (skip Markdown parsing) |
-m, --markdown |
force Markdown (override stdin auto-detection) |
-t, --title <text> |
page title for piped input (default stdin) |
-l, --lang <lang> |
syntax-highlight language for plain mode (go, json; text = none) |
--frame |
wrap plain/ANSI output in a terminal-window frame, implies --plain (share-ready "screenshot") |
--safe |
disable raw-HTML passthrough — use for untrusted Markdown |
--template <selector> |
page presentation: default, reader, notebook, or a local Go HTML template file |
--version |
print the release version (html devel for local builds) |
Run html --help for the full list, including the report-mode flags (--mode, --layout).
Piped input is auto-classified. A high-confidence structural signal — a fenced code block, a GFM table, or a setext heading — makes it Markdown; otherwise it stays plain text, so scripts, diffs, JSON, YAML, and logs are rendered faithfully rather than mangled. Normal document rendering refuses binary input (a NUL byte, or >10% non-text bytes); report mode can render a safe hex/ascii binary preview. Force the document mode with -m / -p.
Files are decided by extension: .md / .markdown → Markdown, everything else → plain.
GitHub-style alerts render as themed callouts in trusted and safe Markdown:
> [!WARNING]
> Back up the destination before replacing it.The supported alert types are NOTE, TIP, IMPORTANT, WARNING, and
CAUTION.
Rendered pages are cached under ~/.config/html/cache/ and reused until the source changes. Use -f to force a re-render, or -o <path> to write the HTML somewhere stable to share or attach.
For trusted cached file renders, relative Markdown links are resolved against the
source document and emitted as absolute file: URLs. Explicit -o/stdout
output and --safe mode preserve links as written.
Cache validity includes the source bytes, not only modification times. Cache
directories are private to the current user. Stable outputs are written
atomically, and --output refuses to overwrite the input or selected template
through the same path, a symlink, or a hardlink. Template selection, source bytes,
and the template-contract version participate in cache freshness; editing a
template invalidates the cached page even when its input is unchanged.
Cache metadata also binds the page bytes, so interrupted or interleaved template
writes are treated as stale instead of reusing a mismatched page.
Omitting --template (or selecting default) preserves the existing page.
reader adds a paper-like article with a sticky contents column on wide screens.
notebook presents top-level Markdown alerts in the margin. Consecutive and tall
notes reserve their own space without moving or duplicating source content;
nested alerts stay with their enclosing block. Contents and notes return inline
on narrow screens and in print. Both layouts retain appearance controls, code
highlighting, copy buttons, heading links, and existing TOC settings.
html --template reader -n notes.md
html --template notebook -n notes.md
html --template ./page.html.tmpl -o page.html notes.mdThe two reading layouts require ordinary Markdown. Use -m when forcing Markdown
for stdin or another extension. Plain/framed modes and report composition are
incompatible with them. --layout still selects report composition independently
of page presentation. Custom templates can wrap Markdown, plain/framed output,
and reports. Explicit --template is incompatible with --plan.
Any other selector is a template file resolved from the working directory. Use
./reader for a file named reader. Templates own the complete document and
choose which of these slots to include:
| Slot | Value |
|---|---|
.Title |
Ordinary text, automatically escaped |
.Content |
Rendered body HTML, including framing when selected |
.TOC |
Optional Markdown navigation, respecting TOC settings |
.Data |
One complete decoded JSON value, or nil for non-JSON input |
.Head |
Complete head contents: title, embedded CSS, pre-paint theme script |
.Controls |
Existing appearance controls |
.Scripts |
Existing copy, heading-link, and report behavior scripts |
For example, a complete minimal wrapper is:
<!DOCTYPE html>
<html lang="en">
<head>{{.Head}}</head>
<body>
{{.Controls}}
<article class="markdown-body">{{.TOC}}{{.Content}}</article>
{{.Scripts}}
</body>
</html>
Ordinary Markdown .Content excludes the separately supplied TOC. Report-internal
navigation stays inside .Content; reports have no separate .TOC. Preserve the
markdown-body class when using the embedded article styling and heading links.
Omitting .Head, .Controls, or .Scripts also omits the features in that slot.
Templates use standard-library html/template, including loops, conditionals,
and inline {{define}}/{{template}} blocks. Missing map keys fail execution.
There are no filesystem includes, network access, command execution, or arbitrary
HTML-trust functions. JSON numbers retain their original precision, and values
are contextually escaped. Input must contain exactly one JSON value, not JSONL
or JSON followed by other text; use .Data to render data independently of
.Content.
Template authors are trusted. --safe protects Markdown input and image handling;
it does not sandbox an explicitly selected template. A template can itself emit
active HTML or external resources. External template source is read once per
invocation and limited to 1 MiB. Custom output is limited to 64 MiB. Parsing and
execution finish before stdout, an output file, or cached HTML is published, so
template errors cannot publish partial pages or replace existing output.
The template examples contain two complete presentations and two compatible datasets. All four combinations work without Go changes:
html --template examples/templates/catalog.html.tmpl -o catalog-instruments.html examples/templates/instruments.json
html --template examples/templates/catalog.html.tmpl -o catalog-field-kits.html examples/templates/field-kits.json
html --template examples/templates/comparison.html.tmpl -o comparison-instruments.html examples/templates/instruments.json
html --template examples/templates/comparison.html.tmpl -o comparison-field-kits.html examples/templates/field-kits.jsonNormal document rendering is the default. Report output is opt-in through
--plan, --mode, --layout, or --planner. The deterministic planner is the
default and performs no network request.
The optional LLM planner requires an explicit --planner auto or --planner llm
plus both --llm-url and --llm-model. The URL must be HTTP(S). A request may
send analysis metadata and up to 8 KiB of input to that endpoint:
html data.json --plan --planner llm \
--llm-url https://example.invalid/v1/chat/completions \
--llm-model example-model --llm-timeout 10sFor two-column record data with one category column and one finite numeric
column, --mode chart renders a bounded, accessible horizontal bar chart and
keeps the full data table alongside it. Inputs with more than 1,000 rows or 24
visible categories get an inline chart diagnostic while the summary and table
remain available.
In automatic report mode, an H2 or H3 section whose complete body is an ordered list becomes a timeline. The planner preserves all other Markdown as article content and validates that the components still own the original source bytes.
Run the generated browser QA suite with:
just qa-browserThe suite regenerates .work/html-qa/, uses the source-owned chromedp helper at
tools/chromedp-capture, captures desktop/mobile PNGs, checks console errors and
overflow, and writes JSON metrics under .work/html-qa/browser/.
To compare supported Markdown fixtures with GitHub's rendering API, run the
opt-in network check while authenticated with gh:
just qa-github-markdown~/.config/html/config.json — every field is optional; a missing file means default behavior. This is valid JSON containing every supported key:
{
"open_command": "firefox",
"max_width": "60rem",
"default_theme": "dark",
"default_palette": "blue",
"default_code_theme": "dracula",
"toc": true
}open_command defaults to the platform launcher. max_width accepts a CSS
length. Theme values are light, dark, or auto; palettes are sepia,
blue, green, rose, or catppuccin;
default_code_theme is a Chroma style.
Omit toc to retain automatic table-of-contents selection.
Raw HTML in Markdown is passed through by default for trusted local files. For
untrusted input, pass --safe. Safe mode strips raw HTML, performs no local
image reads or image fingerprinting, and renders Markdown images as escaped,
non-fetching placeholders with no src. Ordinary links remain clickable.