A native markdown reader. Deliberately not an editor. Free for noncommercial use — see Licence.
It exists so that several .md files can be read side by side without opening a
browser or an editor: it starts instantly, keeps each file in a tab, and builds a
navigable outline from the headings so long documents stay manageable. A
```mermaid block draws as a diagram rather than showing its source; one that
fails to render falls back to that source.
With Homebrew, on macOS — no Rust toolchain needed:
brew tap gaborini/marsdown
brew trust --formula gaborini/marsdown/marsdown
brew install marsdownAll three commands are needed: Homebrew 6 ignores a third-party tap until it is
trusted, so brew install on its own fails. Trusting the one formula rather than the
whole tap is the narrower grant, and it does not extend to anything added here later.
That installs Marsdown.app and puts a marsdown command on the PATH. The binary
inside is universal: Apple Silicon and Intel. There is no Gatekeeper prompt to click
through — a formula install is not quarantined, so it opens on the first try.
The bundle lives in Homebrew's Cellar rather than /Applications, so it has a proper
dock icon and answers to open -a Marsdown, but it will not appear in Launchpad or
Spotlight. That is the trade for not signing the app with a paid Apple Developer ID.
With a Rust toolchain, from crates.io:
cargo install marsdown-reader # installs a command called `marsdown`The crate is marsdown-reader because plain marsdown on crates.io belongs to an
unrelated markdown-to-HTML parser. The command it installs is marsdown.
Or from a checkout:
cargo run -- README.md
cargo build --releaseBoth cargo routes need a stable Rust toolchain, and neither builds an application
bundle — see the platform note below about what that costs on macOS.
Tested on macOS only. There is no platform-specific code and every dependency
is cross-platform, so Linux and Windows are expected to work — but expected is not
the same as tried, and nobody has tried. Two known platform differences: macOS takes
an application's icon from its bundle rather than from the window icon Marsdown sets
at runtime, so the Homebrew install — which ships a bundle — has an icon while a
cargo-built binary does not; and the file-open dialog goes through the XDG desktop
portal on Linux, so a xdg-desktop-portal service has to be running there.
marsdown a.md b.md| Key | Does |
|---|---|
Ctrl/Cmd+O |
Open files — several at once |
Ctrl/Cmd+W |
Close the current tab |
Ctrl/Cmd+B |
Show or hide the outline |
Ctrl/Cmd+F |
Find in the document |
Enter, F3 / Shift+F3 |
Next / previous match |
Escape |
Close the find bar |
Ctrl+Tab, Ctrl+Shift+Tab |
Next / previous tab |
Ctrl/Cmd+1…9 |
Jump to a tab |
Ctrl/Cmd+= / - / 0 |
Larger / smaller / default text |
↑ ↓ |
Scroll a few lines |
PageUp, PageDown, Space, Shift+Space |
Scroll a screenful |
Home, End |
Start / end of the document |
Clicking an outline entry scrolls to that heading. Middle-clicking a tab closes it.
Scroll position is remembered per tab. http(s) links open in the system browser.
Files dropped on the window are opened, and an open file that changes on disk is
reloaded on its own — the point being to read a document beside the editor writing
it.
The status bar has a typeface picker: the system default plus six faces, serif first, each labelled with which it is because "Charter" and "Optima" do not say.
A word on how they were chosen, because "ergonomic" is a claim worth being careful with. There is very little rigorous evidence that any particular typeface measurably improves reading speed or comprehension for normally-sighted readers, and most assertions otherwise are marketing. What can honestly be said is which faces were designed for extended reading on screen, and what features serve that: a large x-height, open apertures, strokes that survive small sizes, and letterforms you cannot confuse.
| Face | Why it is here |
|---|---|
| Charter | Matthew Carter, 1987, for low-resolution output. Narrow and sturdy. |
| Georgia | Carter, 1993, drawn for the screen: large x-height, serifs that do not thin. |
| Palatino | Zapf's humanist book face. Open counters, calm over long runs. |
| Verdana | Carter, 1996, for legibility at small sizes. Keeps I, l and 1 apart. |
| Avenir Next | Geometric humanist sans: even rhythm, clear apertures. |
| Optima | Zapf's humanist sans, modulated strokes for continuous text. |
These are named system fonts, not bundled files, so nothing is added to the binary and there is no font licence to honour. The cost is platform-dependence: a name the system does not have falls back. That was tested rather than assumed — a deliberately bogus name renders in a legible fallback face, not blank and not in tofu boxes.
Inline code stays in the reading face, at the reading weight and the paragraph's
size. A monospaced word dropped into a serif paragraph reads as a foreign object —
different letterforms, different rhythm, and a different apparent size even at the
same point size. Like this is set apart by a tinted background alone, plus a deeper
text colour where the palette leaves room for one.
That proviso is real rather than decorative. In the plain light theme the body text is
already #000000, so code cannot be darker than it and the background does all the
work. Solarized Light has room (#004658 against #3a606d prose) and so does
Catppuccin Latte (#303256 against #4c4f69).
Code blocks do stay monospaced: there the column alignment is the point, and the syntax highlighting is built for it.
The status bar has a palette picker. It offers all 22 palettes iced ships — Solarized, Gruvbox, Catppuccin, Tokyo Night, Kanagawa, Nord, Dracula, Moonfly, Nightfly, Oxocarbon, Ferra and the plain light and dark — plus "Follow the system", which is the default.
Light palettes are listed before dark ones and each is marked with a sun or a moon, because light or dark is the first thing you decide and names like Ferra and Oxocarbon give no clue which they are.
ICED_THEME sets what "follow the system" resolves to, so
ICED_THEME='Solarized Dark' marsdown README.md starts there. An explicit choice in
the picker always wins over it.
Code blocks stay dark in every palette. That is not an oversight: the markdown widget bakes in a dark syntax-highlighting theme at parse time and does not let it be swapped, so a light code block would leave the code illegible. Inline code does follow the palette.
Inline code sits on the palette's weaker background step, which puts it at a
contrast ratio of 1.23–1.30 against the page. The strong step it used before was
at 1.58–1.83, which punched holes through paragraphs in a document where every
other phrase is like this. The code text itself stays above 7.7:1 against its own
fill in every palette, well past the 4.5:1 that WCAG AA asks for.
None of the three pickers is remembered between runs yet.
No editing, no saving, no export, no LaTeX, no plugins, one window. No recent-files list and no session restore. A mermaid diagram is drawn, not typeset: its text cannot be selected or searched.
Two limits in search are worth knowing, and both come from markdown::Row keeping
its cells private with no accessor. Matches inside a table body are highlighted —
the default renderer draws them through our viewer — but they are not counted and
cannot be stepped to. Matches in image alt text are neither highlighted nor
counted. Search navigation also lands on the block containing a match rather than
the exact line, because the widget markdown text renders to reports no per-span
position to any operation.
CommonMark and GFM come from iced's built-in markdown widget, so tables,
strikethrough, task lists and syntax-highlighted code blocks come for free.
The interesting part is jumping to a heading, because iced 0.14 gives you no way to
ask where a widget is. container is the only widget that both takes an Id and
reports its bounds to an operation, and the text::Rich that markdown renders to
reports nothing at all — so a custom markdown::Viewer wraps every heading in an
identified container, and selector::find_all measures those anchors and the
scrollable in a single pass.
Search highlights at span level, including matches that straddle a **bold**
boundary or a wrapped line. Matches are identified by the address of the text run
they live in rather than by a counter, because the default table renderer calls
back into our viewer lazily for row cells — so anything counted during rendering
drifts. src/search.rs explains it at length.
src/app.rs state, messages, update and view
src/render.rs the anchoring markdown viewer
src/measure.rs widget measurement and scroll arithmetic
src/outline.rs headings from the parsed document
src/search.rs match indexing and span-level highlighting
src/stats.rs words, characters, reading time
src/appearance.rs text size and colour scheme
src/watch.rs noticing on-disk changes
src/document.rs a parsed document and what is derived from it
src/tab.rs one open file
src/icon.rs the window icon and bundle icon, drawn in code
src/ui/ tab bar, find bar, sidebar, status bar
examples/iconset.rs renders src/icon.rs to the PNGs an .icns is built from
PolyForm Noncommercial 1.0.0. Copyright 2026 Gabor Lepsenyi.
Read it, learn from it, run it, change it, hand it to a friend — all free, for any noncommercial purpose. Charities, schools, public research bodies and government institutions count as noncommercial regardless of how they are funded.
Making money with it does not. If you want to use Marsdown commercially, ask for a licence.
This is deliberately not an open-source licence: OSI-approved terms all permit commercial use, and that is the one thing being held back. Practical consequences worth knowing:
- crates.io accepts it. It does not require OSI approval, and hosts crates under
this identifier today.
cargo install marsdown-readerworks today. - Marsdown is a binary, not a library, so nobody can take it as a dependency and end up bound by these terms without noticing. Installing it is a deliberate act.
- Distributions have to carry
LICENSE.md, including theRequired Notice:line in it. That is the mechanism the licence uses to make the copyright notice travel with the code. - Corporate licence scanners flag noncommercial terms, and they are right to. If you are behind one, that flag is the licence working as intended — ask instead.
- 614 dependencies, and all but four are permissive — MIT, Apache-2.0, BSD, Zlib, ISC, CC0 or a choice including one of those — so none of those constrains this choice or is violated by it. iced, pulldown-cmark and notify are MIT; rfd is CC0-1.0.
- The other four — cssparser, cssparser-macros, selectors and dtoa-short, reached through lol_html, which merman-core requires — are MPL-2.0. That is file-level copyleft: it does not change Marsdown's own licensing, and the obligation it carries is a notice, which THIRD-PARTY-NOTICES.md satisfies.