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) |
--code-theme <name> |
chroma style for code blocks (dracula, monokai, nord; empty = github/github-dark) |
--frame |
wrap plain/ANSI output in a terminal-window frame, implies --plain (share-ready "screenshot") |
--safe |
disable raw-HTML passthrough — use for untrusted Markdown |
--stdout |
write the final HTML document to stdout without opening |
--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, a GFM task list, a multi-line blockquote, a setext heading, or an ATX heading followed by a blank line — 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
canonical source document and emitted as absolute file: URLs, including links
to parent directories. Explicit -o/stdout
output and --safe mode preserve links as written.
File inputs resolve symlinks once. The canonical target supplies the input type, fallback title, syntax language, image/link base directory, and cache identity; explicit mode, title, and language options retain precedence.
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. Ordinary cached
page read or hash failures cause regeneration; failures to establish private
permissions or publish the replacement remain errors. Already-private cache
entries do not require a permission change. Orphan temporary files are cleaned
up after seven days so recent concurrent writes are left alone.
Report analysis, validated plans, render options, image dependencies, and
image diagnostics are prepared before cache lookup. A fresh report skips HTML
generation; force mode and explicit destinations render again. Rendered chart
options, article/timeline source references, component order, and effective CSV
preview limits participate in cache identity; unused planner metadata does not.
Image diagnostics are retained even when rendering or publication fails.
HTML_CACHE_DIR overrides the rendered cache root and places optional planner
cache entries under its plan-cache/ subdirectory. Without it, planner entries
use ~/.config/html/plan-cache/, with a temporary fallback when HOME is unavailable.
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), and
--llm-timeout must be a positive duration. 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 10sCSV/TSV reports validate and count the complete input while retaining a bounded prefix for display. Defaults retain at most 1,000 data rows and 100,000 cells, including the header. Rows keep every column; a header larger than the cell budget produces a diagnostic instead of a table. Quoted multiline fields are supported, and malformed records beyond the preview still invalidate the input. Statistics describe the complete input. The preview shows retained/total counts; search, sorting, and other table controls affect only displayed records. These limits bound retained cells and table elements, not absolute process memory.
For 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 data table alongside it. Truncated CSV/TSV previews, inputs with more
than 1,000 rows, or more than 24 visible categories produce an inline chart
diagnostic while the summary and available table remain visible. Explicit chart
category selections can use numeric labels; automatic inference stays conservative.
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. An H1 ends the preceding semantic section. Article ranges in pages, tabs, and slides render from one full-source Markdown parse, preserving reference links and global heading IDs. Invalid source references fall back to one whole-document article. Contents navigation uses the rendered heading boundary.
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 or unavailable HOME means default behavior. Existing unreadable
or malformed files report an error. This is valid JSON containing every supported key:
{
"open_command": "firefox",
"max_width": "60rem",
"default_theme": "dark",
"default_palette": "blue",
"default_code_theme": "dracula",
"csv_preview_rows": 1000,
"csv_preview_cells": 100000,
"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.
csv_preview_rows and csv_preview_cells accept positive overrides; omitted or
zero values use embedded defaults of 1,000 data rows and 100,000 cells including
headers. Negative values are rejected. Preview limits also affect optional
planner cache identity.
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.