CLI tool
henriklovhaug/md-tui avatar
henriklovhaug/md-tui

md-tui is a keyboard driven Markdown reader for the terminal, built on ratatui

Markdown renderer in the terminal

555 stars33 forksRustAGPL-3.0

At a glance

What is it?
md-tui is a Rust terminal application for viewing and navigating Markdown files, with vim style scrolling, a file tree, search and link selection modes, tree-sitter syntax highlighting in fenced code blocks and optional inline images. It reads ~/.config/mdt/config.toml for both keys and colours, and it is licensed AGPL-3.0-or-later, which is the one decision worth checking before you adopt it.
Who is it for?
Adopt md-tui if you read Markdown inside a terminal over SSH and want vim style navigation, a file tree and link following without leaving the shell, since the key map and the config file are both fully documented. Do not adopt it if you intend to embed it in a closed source product, because the licence is AGPL-3.0-or-later, or if you are on a terminal without a Nerd Font, since that is a stated requirement for the icons.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 21 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Markdown reader, not a Markdown editor

md-tui is described as a terminal-based application for viewing and navigating Markdown files, and the README is explicit about the emphasis: keyboard navigation and functional link navigation. The capability list is short. Keyboard-driven navigation, internal and external links, a file tree for browsing Markdown files, search and link selection modes, and optional image rendering depending on terminal support.

So the product boundary is a reader. There is an edit action, but it hands the file to $EDITOR and comes back, rather than giving you an editing buffer.

That is a deliberate narrowing, and it is worth understanding why it is a sensible one. A terminal Markdown reader competes with the browser and with your editor's preview pane, and it wins on a specific axis: it is fast, it works over SSH, it needs no rendering engine, and it never leaves the keyboard. Everything in the README is consistent with that axis. Search is incremental and keyboard driven, links are followed in place, and the file tree exists so a directory of notes can be browsed without leaving the program.

The build stack matches the goal. The interface is ratatui 0.30.2 with the macros, all-widgets and serde features, the terminal handling is crossterm 0.29.0 built with the use-dev-tty feature so input is read from the controlling terminal rather than standard input, and inline images come from ratatui-image 11.1.0 with default-features turned off and the image-defaults and crossterm features enabled.

The binary is named mdt and lives at src/main.rs, and the package version in Cargo.toml is 0.11.0 on edition 2024, authored by Henrik Tøn Løvhaug.

The key map is vim shaped, and the modes are the reason to switch

The default key bindings are a full table in the README, and the shape of it is worth reading as a design statement. Scrolling uses j and k for one line, h and l for half a page, d and u for a full page, plus the arrow keys, and g and G jump to the top and bottom of the file.

Then there are the modes, and this is where md-tui does something that a plain pager does not.

Pressing s or S enters select link mode, and the two keys do different things. The README describes select_link as searching downwards until it finds one or selecting the last link in the document, and select_link_alt as finding the link two thirds up the page, searching then for the closest in both directions. So one key gets you the next link below and the other gets you the nearest link to the line your cursor is on, searching upward and downward. On a document with a table of contents and dozens of inline references, that difference is the whole ergonomics of the tool.

Pressing D enters select details mode, which cycles through <details> blocks, and Enter folds or unfolds the selected one. Pressing K hovers, previewing a link target without following it, which is the difference between reading a document and chasing links blind. There is also select_annotation for CriticMarkup annotations, where Enter reads the selected comment, and the README's example file section shows the mode list includes a theme action on T.

The remaining keys are the ones you would expect from a file browser: t returns to the file tree, b goes back to the previous file or to the file tree when there is none, o sorts the tree, e opens the file in $EDITOR, and q quits.

Two modes of link following, internal and external

The link handling is the feature the project names first, and it splits in two.

Internal links are followed inside the program, and the file tree and the back key are what make that work. When you follow a link to another Markdown file, the document view swaps to it, and b takes you back to where you came from, with the file tree as the fallback when there is no previous file in the session.

External links are the other half. The open crate at version 5.4.4 is in the dependency list, which is what handles handing a URL to the system's default handler. The hover action is the safety net for that: K previews the target without following it, so you can see where an external link goes before your browser opens.

The mode structure is what keeps those two from colliding. Search has its own key, f or /, and the results are stepped through with n and N for next and previous. A search hit, a link and a details block are all things that could be selected, and the program separates them by mode rather than by a single cycling selector. Enter means different things in each mode, and Esc returns to normal mode.

One caveat from the README is worth repeating because it is easy to trip over. Overriding a default key is undefined behaviour if that key does not get reassigned, and actions can only be assigned to single characters. Space, function keys, ctrl+key and backspace will not take effect and the default stays in use. The set of keys that are not configurable at all is listed separately: Enter, the arrow keys, Escape, the question mark for the help menu, q to quit, and / for search.

Syntax highlighting comes from tree-sitter, and it is a compile time choice

Syntax highlighting in fenced code blocks is handled by tree-sitter, and the Cargo.toml shows how that decision is structured.

The tree-sitter feature is on by default and pulls in a grammar crate per language: tree-sitter-bash, -c, -cpp, -css, -diff, -elixir, -go, -html, -java, -javascript, -json, -lua, -ocaml, -php, -python, -rust, -scala, -typescript and -yaml. The README lists the languages it highlights, which is Bash/sh, C/C++, CSS, Elixir, Go, HTML, Java, JavaScript, JSON, Lua, Luau, OCaml, PHP, Python, Rust, Scala, TypeScript and YAML.

The important detail is that these are optional dependencies behind a feature flag, not unconditional ones. Turning off the default features drops the grammars and the network stack together, which is what makes a smaller build possible for someone who does not want tree-sitter's parsing cost. The other default feature is network, which enables ureq 3.4.2, the HTTP client used for fetching remote content.

The second half of the highlighting story is the colour control. The README notes that the code_hl_* keys colour the highlighting inside fenced code blocks, so the colour of a token class is a configuration key rather than a hardcoded value. The general settings block in the config file also carries code_bg_color and code_fg_color for the code area itself, with a default background of #2A2A2A and a red foreground.

For a project that presents other people's documents, syntax highlighting with configurable token colours matters more than it might seem. A document that highlights every identifier in a different hue is much harder to read than one that leaves code mostly monochrome.

One config file holds keys, colours, themes and reading position

The program checks for the file ~/.config/mdt/config.toml at startup, and everything the README documents about customisation lives there.

Keyboard actions are the first block, and the defaults are the ones from the key table expressed as named actions:

toml
# Keyboard actions
up = 'k'
down = 'j'
page_up = 'u'
page_down = 'd'
half_page_down = 'l'
half_page_up = 'h'
top = 'g'
bottom = 'G'
search = 'f'
search_next = 'n'
search_previous = 'N'

The second block is presentation and behaviour, and the defaults are opinionated in a way that tells you what the author cares about:

toml
# General settings
width = 100 # Set to 0 for full terminal width
gitignore = false
alignment = "left" # "center" | "right"
help_menu = true # false hides it
document_header = false # true shows the current document path
file_tree_directory_header = false # true shows the browsing directory in the file tree
heading_style = "hashes" # "bars" renders H2-H6 with indented accent bars
scrollbar = true # false hides the document position indicator
image_max_height = 20 # maximum terminal rows used by an inline image
remember_position = true # restore recently viewed documents at their last position
position_cache_ttl_minutes = 60 # ignore older positions; 0 means never expire

Three of those deserve comment. The width setting caps the rendered document at 100 columns and 0 means the full terminal width, which is the kind of line length control that a browser does not need and a terminal reader does. The gitignore flag, off by default, makes the file tree respect .gitignore files, which is exactly what you want in a notes directory that also happens to be a repository. And the position cache with a one hour TTL means reopening a document returns you to where you were reading, but only if you were there recently.

The theme system is separate from the config file and worth its own look. Pressing T opens a chooser with j and k or the arrow keys and Enter to apply, s to save the current palette under a name, and x to delete a saved palette with a confirmation. Palettes live as JSON in ~/.config/mdt/themes/<name>.json, and the built-in options are Custom, which uses the colours in config.toml, Match terminal, which resets colours to the terminal's palette, and Light, Dark, Warm light and High contrast as accent palettes that do not change the terminal background. The chosen theme is remembered across runs.

The colour settings have one sharp edge: setting a colour to the empty string will not remove it, it leaves the default in place. To remove a colour you set it to reset.

The Nerd Font requirement is the one you will hit first

The requirements section lists two things: a terminal, and a Nerd Font.

The second is not decorative. A file tree with file type icons, a scrollbar and styled headings all need glyphs that a default terminal font does not contain, and the README's installation table and capability list both assume those glyphs are present. A terminal without a patched font renders the icons as boxes or nothing at all, and there is no plain ASCII fallback mentioned anywhere in the README.

That is a real constraint, and it is worth checking before installing anything. Nerd Fonts are patched font families that add the icon ranges, and they are widely available, but if your terminal is on a locked-down corporate image or a minimal Linux console, you may not have one available.

Installation itself is straightforward, with a prebuilt binary and install script on the release page, plus a table of package managers: pacman -S md-tui on Arch, cargo install md-tui --locked through Cargo, pixi global install md-tui from Conda-Forge, brew install md-tui through Homebrew, and a flake for Nix.

Two entry points exist at runtime. mdt <file.md> opens a specific file, and bare mdt searches recursively from where it was invoked for Markdown files and presents them in the file tree. Content can also be piped in, so cat README.md | mdt reads from standard input, which is the same use-dev-tty arrangement on the crossterm side that lets the program still capture keys while the document arrives through the pipe.

Where md-tui is the wrong choice

The first limit is the licence, and it is the reason to read this section first. The Cargo.toml records license as AGPL-3.0-or-later and the repository listing agrees. AGPL is copyleft with a network clause, so if you distribute md-tui inside a product, or offer it as a service, the corresponding source obligations reach your own code. That is a licensing note rather than legal advice, but it is a different kind of grant from MIT or Apache and it is the difference between a tool you can vendor and one you cannot.

The second limit is the terminal itself. md-tui renders into a character grid, so anything that needs real layout, precise typography or embedded vector content is out of reach. The inline image support is described as optional and dependent on terminal support, and ratatui-image is configured with default-features turned off, which means the image protocols available are the ones the terminal happens to support. An inline image is a terminal row budget, not a picture, and image_max_height defaults to 20 rows.

The third is that the reading is one direction only. The edit action shells out to $EDITOR and returns, so the round trip is a context switch rather than an in-app edit. If your workflow is writing as much as reading, the browser preview or an editor plugin is the better tool, and md-tui is the better tool for reading a lot of documents quickly.

The alternative in the same space is a pager. bat, with its syntax highlighting and line numbers, is the tool most people reach for, and it is lighter and faster because it does far less. The difference in approach is that md-tui is a full screen application with a file tree, modes and link following, where a pager is a filter that streams a file to the screen. If you only need to read one file and follow a couple of links, a pager is the shorter path. If you browse a directory of notes, or you want to jump between links inside a long document without a mouse, the mode structure is the reason md-tui earns its place.

There is also the more recent crop of the same idea in the ecosystem, the Markdown viewer built into modern terminal multiplexers. Those inherit your existing session, which md-tui cannot, but they also give you less control over the key map and the colours.

Aggressive release cadence, one binary, and a small dependency list

The maintenance picture is current. The last push was on 2026-10-02 and the repository is not archived, and the release history shows v0.11.0 on 2026-10-02, v0.10.4 on 2026-08-31 and v0.10.3 on 2026-06-27. Three releases in a quarter, with the newest landing on the same day as the last push, which is what a project in active use looks like.

The repository layout is compact. There is src/, a tests/ directory, an examples/ directory holding demo.rs, a Cargo.lock, a CHANGELOG.md, a CONTRIBUTING.md, a flake.nix and flake.lock for the Nix path, and dist-workspace.toml for distribution packaging. The README's table of contents also lists a Use as library section, and the presence of examples/demo.rs suggests the crate can be consumed as a library rather than only run as the mdt binary, though the README section itself is beyond what the documentation here covers.

The dependency list is small and mostly infrastructure rather than content: ratatui and crossterm for the interface, ratatui-image and image 0.25.10 for pictures, config 0.15.27 with the toml feature for reading the config file, dirs 7.0.0 for locating configuration directories, notify 8.2.0 for filesystem events, open 5.4.4 for handing links to the desktop, pest 2.9.2 with pest_derive for parsing, serde and serde_json for data, strsim 0.11.1 for string similarity, unicode-width 0.2.2 for character widths, itertools 0.15.0, and better-panic 0.3.0 for panic handling.

The upgrade cost is low in the mechanical sense: a single binary, or a cargo install, and no configuration migration is described in the release notes. The cost that matters is behavioural. A 0.x application that ships three releases a quarter can change its key map or its default rendering between versions, and the README's own warning about overriding default keys applies here too. Pinning the version is the sensible precaution, especially if your muscle memory depends on it.

Editorial conclusion

Adopt md-tui if you read Markdown inside a terminal over SSH and want vim style navigation, a file tree and link following without leaving the shell, since the key map and the config file are both fully documented. Do not adopt it if you intend to embed it in a closed source product, because the licence is AGPL-3.0-or-later, or if you are on a terminal without a Nerd Font, since that is a stated requirement for the icons. Verify first that the binary name and entry point work for you, that your terminal renders the glyphs the file tree needs, and that the two default features, tree-sitter and network, are ones you want compiled in.

Frequently asked questions

How do I install md-tui?

Prebuilt binaries with an install script are on the release page, and there are package manager routes: pacman -S md-tui on Arch, cargo install md-tui --locked through Cargo, pixi global install md-tui from Conda-Forge, brew install md-tui through Homebrew, and a flake for Nix. The stated requirements are a terminal and a Nerd Font.

How do I open a Markdown file and browse a directory in md-tui?

Run mdt <file.md> to open a specific file, or run bare mdt to search recursively from the current directory and present the results in a file tree. Content can also be piped in, for example cat README.md | mdt.

Which languages get syntax highlighting in md-tui?

Bash/sh, C/C++, CSS, Elixir, Go, HTML, Java, JavaScript, JSON, Lua, Luau, OCaml, PHP, Python, Rust, Scala, TypeScript and YAML. The highlighting comes from tree-sitter, which is behind the tree-sitter feature and is enabled by default alongside the network feature.

Where is the md-tui configuration file and what can I change in it?

The program reads ~/.config/mdt/config.toml at startup. It holds keyboard actions such as up, down, page_up, search and select_link, plus general settings including width, gitignore, alignment, heading_style, scrollbar, image_max_height and the remember_position and position_cache_ttl_minutes pair. Saved colour palettes live separately in ~/.config/mdt/themes/<name>.json.

What licence is md-tui released under?

AGPL-3.0-or-later, recorded in both the Cargo.toml and the repository listing. The package version is 0.11.0, the latest release v0.11.0 was published on 2026-10-02, and the last push was on 2026-10-02 with the repository not archived.

Official sources

  1. henriklovhaug/md-tui on GitHub
  2. Issues
  3. License: AGPL-3.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/henriklovhaug-md-tui.svg)](https://hysenlabs.com/projects/henriklovhaug-md-tui)

Was this useful?