IntroSo, Emacs 31 has been released, and a lot of shiny new stuff isthere, ready for us to play with.You probably heard of this new markdown-ts-mode and decided to checkit out. And guess what? On Emacs version 31, this is marked as anexperimental mode. What does this mean? Should you use it or not? Isthis ready? Is this just a sketch of a mode?Treat this post as a quick guide to getting this mode up and runningand helping yourself find answers to these questions.Where is it in terms of features?This is an experimental mode, right? You need to opt in, so probablynot everything will work flawlessly yet, and it needs more testing andfeedback.That said, don't let this title mislead you. This does not mean themode is premature in terms of features. As you will see, this is avery feature-rich mode. This mode already covers all of thehttps://commonmark.org/ spec, as well asmost ofhttps://github.github.com/gfm/, withsome extras like code blocks even for non-ts-modes, like elisp,table of contents utilities, and interfaces with external converters,such as pandoc and gfm.Before deep diving into it yourself, you may need some help simplyturning this mode on. Tree-sitter is tricky. It might even be yourfirst time with tree-sitter, so a quick "install guide" is on ouragenda.Where is it? Do I need to install the mode?Experimental means Emacs does not enable the mode by default so it isnot there waiting for you to simply open a .md file or call it withM-x markdown-ts-mode RET. You need to load this library.As always on Emacs, there's more than one way of doing everything, Iam a big fan of use-package so I tend to use it to organize myinit file. Here is my suggested initial setup:(use-package markdown-ts-mode :ensure nil :mode ("\\.md\\'" "\\.mdx\\'" "\\.markdown\\'") :config (require 'markdown-ts-mode-x))Or if you keep use-package out of your tool belt:(autoload 'markdown-ts-mode "markdown-ts-mode" nil t)(dolist (re '("\\.md\\'" "\\.mdx\\'" "\\.markdown\\'")) (add-to-list 'auto-mode-alist (cons re 'markdown-ts-mode)))(with-eval-after-load 'markdown-ts-mode (require 'markdown-ts-mode-x))Now both the mode and the x (nice extra goodies) libraries areloaded, and you can simply visit your Markdown files using it.If you want to experiment with it without touching your ownconfiguration, do the following:Save the above content in a file like testing.el.Call emacs with emacs -Q --load 'testing.el'.And there you have it, a bare Emacs session with your testing groundset up. This is what I will use for the rest of this guide.IMPORTANT: there's NO NEED to download or add thispackage to your package manager. The (now very old and archived)MELPA Repositorywill refuse to install on Emacs version 31 onward and is very, verypoor in terms of features. If you are using this, you're not usingthe new built-in markdown-ts-mode. Right? Let's continue.Opening our first markdown fileIn order for you to "see what I see", we need some pictures. If it isthe first time you're using a tree-sitter-based mode, let me warn you:although tree-sitter is wonderful, fast, and feature-rich, it comeswith its own set of tasks to complete and perhaps debugging skills ifit needs help. I will try to cover some here; I will forget others forsure.For this guide, I will be using this testfile.The repository where it is hosted is our laboratory. No code livesthere, remember, all code is in Emacs itself.Now go ahead and open the test.md file.IMPORTANT: At this point, many things can happen. If you havethe grammar for markdown installed in your system, the file isalready opened. You could, though, be prompted, as I am here, withthis:It means Emacs hasn't found a grammar for markdown in my system, inthis case in ~/.emacs.d/tree-sitter/ (which is the default when Istart Emacs with emacs -Q ...). Emacs will offer to install it,which means downloading and compiling it from a repository alreadydefined in markdown-ts-mode's source code. Let's install it withy. Emacs will clone the grammar repository, compile it, and continueto the second grammar. Yes, markdown uses two grammars: the main oneand one for inline parsing. I will allow Emacs to install the secondone with y.Success!What you should be seeing:If not, here is what you should check if something went wrong:Is Emacs compiled with the tree-sitter flag? Use M-: (featurep 'treesit) RET and check if it returns t.Do you have the tooling used for "compiling" grammars, like make,gcc, and others?Tree-sitter needs a package in your distro, usually namedtree-sitter-cli which provides a tree-sitter binary, you cancheck you have it with tree-sitter --version.This is a common headache for all tree-sitter modes. Many peoplelike NOT to compile their own grammars, but instead use somecompiled file from a place they trust, like their own distrorepository, or packages with hundreds of pre-compiled grammars. I willnot dive into it; there are many ways of acquiring grammars, and Iwill stick with "build it yourself" for this guide.See, I kind of tricked you there. I told you that you should be seeingthat, but actually, the "do you see what I see" should look like this:We provide the full file inhere,with several default themes so you can compare whether your setup iscomplete.So, what happened?This is part of the reason markdown-ts-mode is very special.This mode can work not only with markdown, but with all other-ts-modes available! Keep this in mind; we will talk about codeblocks in a while. For now, we need to understand a few things.In your test.md file, we have a special header. It is very common tohave toml or yaml as headers of markdown files.This little guy here:---title: The Official 'markdown-ts-mode.el' Feature Test Fileauthor: Rahul Martim Juliatodate: 2026-03-18version: 0.1.0parsers needed: markdown, markdown-inline, yaml, toml, html, c, javascript, python, ruby, rust---Needs something else to fontify (aka be painted with colors byEmacs). Can you figure out what is missing? If your answer is "weneed a grammar for YAML!", kudos!Whenever something does not fontify correctly in -ts-modes, you'reprobably missing a grammar. And as markdown-ts-mode is made to workwith all available ts-modes, this is no exception.Let's install our yaml grammar with our trusty M-x treesit-install-language-grammar RET yaml.You might see now what I am seeing:Let's agree to it with y. Hmm, it looks like this time, somethingwent wrong with yaml-ts-mode trying to register its preferredgrammar with treesit-install, as there are no suggestions. We couldprovide it manually. But let's check something first. Taking a look atyaml-ts-mode.el, we can check which grammar it expects in its sourcecode:;; from yaml-ts-mode.el(add-to-list 'treesit-language-source-alist '(yaml "https://github.com/tree-sitter-grammars/tree-sitter-yaml":commit "b733d3f5f5005890f324333dd57e1f0badec5c87") t)Awesome! Let's simply evaluate that block and try to install thegrammar again. Or manually provide the sourcehttps://github.com/tree-sitter-grammars/tree-sitter-yaml to ouralready-started interactive session, as I did this time:We then keep going with the defaults with RET RET RET... until thelibrary is installed.After that, reload markdown-ts-mode, or use C-x x g, or re-openthe file you're visiting.What we did here by visiting the source code is pretty rare, and most-ts-modes will automatically suggest the repository from which theyare going to compile. It was nice that this happened, so I can showyou what to do.Now what? We need to do the same M-x treesit-install-language-grammar for every block withoutfontification that we encounter. If you'd like, for our test file wecould use C-x x f to force fontification and be prompted for everymissing grammar used by this file.By now, you should see the entire document fontified as inhere. Sameas previous image:A note on grammarsA -ts-mode is only as good as the tree-sitter grammar behind it.This means every -ts-mode needs to constantly keep up withimprovements to the grammar, which is shared by any editor orprogram wanting to use tree-sitter to parse the language.This also means we are, at some point, dependent on the grammar forcertain constraints and features. Almost all -ts-mode code in Emacsis filled with notes on limitations and the reasoning behind why andhow something obscure is treated the way it is.Emacs mode authors and maintainers always try to suggest the grammarand the SHA commit the ts-mode is prepared to use, either incomments or in the code inside the mode, which is the same as you sawfor the yaml suggestion. Part of maintaining ts-modes is keepingup with newer grammar version changes. We try our best to keep itupdated with the latest versions, but the one we tested against andthat should work as expected is the one in the source file of themode.This is why I think compiling it yourself interactively with Emacs isthe best possible way to guarantee a nice experience.Specifically for markdown-ts-mode, we're using the grammars providedby https://github.com/tree-sitter-grammars/tree-sitter-markdown, asthis is the most complete, maintained, and broadly adopted one, bothby code editors and programs in general. This doesn't mean it is freeof bugs or limitations. Again, we do our best to work around theselimitations and even contribute issues to the grammar and to the coretree-sitter library.I can finally open a markdown file!Congrats! Now what? How often do I need to do all of this? Only once,the first time you use a -ts-mode, or never if you already havegrammars installed by some other method.Now let's see what markdown-ts-mode already provides.A quick look at markdown-ts-mode featuresWe (BTW, this mode is authored by me and Stéphane Marks) provided aneasy-menu feature for quick discoverability of functionalities.You can access it by clicking on Markdown in the mode-line, or, ifyou have menu-bar-mode enabled, on the menu bar, or even Ctrl + Right click (whatever Emacs maps your OS input to) on a buffer usingmarkdown-ts-mode.This is actually this guide's TL;DR, if you want to stop now andexplore it yourself (spoilers ahead).EditingThe fastest way to learn the mode is to type a little of everything.Below is a speed run: what you write, what key does it for you.Marks (emphasis)Markdown is plain text, so you can always type the markers yourself:When you wantYou writebold**bold**bold, alt__bold__italic*italic*italic, alt_italic_bold + italic***both***strikethrough~~gone~~inline code`code`Or let the mode do it: C-c C-x C-f (markdown-ts-emphasize) then asingle key:b bold, B bold with underscoresi italic, I italic with underscoresa bold + italics strikethroughc inline codeSPC remove emphasis at pointIf a region is active, the formatting wraps the region. With noregion, it wraps the word at point, or inserts the pair and dropspoint in the middle.Tip: C-c C-x RET (markdown-ts-toggle-hide-markup) hides themarkers themselves, so **bold** shows as bold. Very nice forreading while editing, like default org-mode.Another tip: M-q fills correctly even inside lists and quotes.HeadingsType them: #, ##, ... up to ######. Setext headings (=== and--- underlines) are recognized, too.Promote and demote without retyping the hashes:M- promote (markdown-ts-promote)M- demote (markdown-ts-demote)And move a whole section, body and children included:M- (markdown-ts-move-subtree-up)M- (markdown-ts-move-subtree-down)TAB on a heading cycles its visibility (outline folding). The modeis an outline-minor-mode citizen, so folding just works. S-TAB ona heading will cycle the visibility of all headings.IMPORTANT: By now, you can see this mode tries, when possible, todraw parallels with org-mode, so Emacs users used to it can havefewer problems adapting to markdown. If these bindings don't suityou, everything can be customized.Listings (lists and checkboxes)Type - item, + item, * item or 1. item.M-RET new list item (markdown-ts-insert-list-item)RET is smart: markdown-ts-newline continues the list for youM- / M- promote/demote the itemC-c C-r renumber an ordered list (markdown-ts-renumber-list)C-c C-c toggle a task checkbox (markdown-ts-toggle-checkbox)M-q fills correctly inside an itemTask lists are the GFM ones:- [ ] not done- [x] doneRaw mode:With markup hidden:Note the bullets and boxes you see if you toggled C-c C-x RET aredisplay only. The buffer still holds - and [x]. Seemarkdown-ts-unordered-list-marker, markdown-ts-checked-checkboxand markdown-ts-unchecked-checkbox.BlocksC-c C-, (markdown-ts-insert-structure) then one key:` fenced code block, prompts for the language~ tilde fenced code blockq block quoted divider (thematic break)t tableIf a region is active, it wraps the region instead of inserting anempty block.With markup hidden:Code blocksThis is the party trick. A fenced block tagged with a language isfontified by that language's own mode:```pythondef hello():return "world"```Missing colors typically means a missing grammar, same story as theyaml header earlier.Better than colors: put point inside the block and you are inmarkdown-ts-code-block-in-context-mode (lighter [code] in themode-line). Inside it:TAB indents like the language doesRET newline and indent like the language doesM-q fills like the language doesM-. jumps to definition via xrefMove to the next/previous blocks with C-c C-v n and C-c C-v p.Non tree-sitter modes work too, elisp included. Knobs:markdown-ts-code-block-modes, markdown-ts-default-code-block-mode,markdown-ts-fontify-code-blocks-natively.An example raw:With markup hidden:TablesInsert one with C-c C-, t or M-x markdown-ts-table-insert-table,which asks you to specify the number of rows and columns to insert.| Column 1 | Column 2 ||----------|:---------|| a | 1 |Inside a table you are in markdown-ts-in-table-mode (lighter [table]) and the keys change:TAB / S-TAB next / previous cell (also formats your table)RET / S-RET next / previous rowM-RET insert row belowM- / M- move rowM- / M- move columnM-S- insert row above, M-S- delete rowM-S- insert column left, M-S- delete columnC-c C-c align the whole tableC-c C-t a set column alignment (left, center, right)C-c C-t t transpose the tablePlus, from the menu: clone rows and columns, CSV/TSV import of aregion and CSV/TSV export of the table.NOTE: There are some limitations when working with tables at themoment, mostly due to how the grammar parses them, so you may bumpinto unfontified stuff while typing. All valid tables according tothe GFM spec should be good to use, though.Links and imagesLinks are the usual [text](url) and [text][ref]. Fragment linkslike [intro](#intro) are clickable and jump to the heading in thebuffer, using GitHub style slugs by default.Images render inline. C-c C-x C-v toggles them(markdown-ts-toggle-inline-images). Seemarkdown-ts-image-max-width andmarkdown-ts-display-remote-inline-images for how big and whetherremote URLs are fetched.Markdown:After C-c C-x C-v:After C-c C-x RET:Moving aroundTAB cycle folding at pointC-c C-n / C-c C-p next / previous headingC-c C-u up to parent headingC-c C-f / C-c C-b next / previous heading, same levelM-x imenu jump to any heading or named code block by completionC-c C-v n / C-c C-v p next / previous code blockmarkdown-ts-default-folding decides how a file opens: everythingshown, or folded.markdown-ts-view-modeM-x markdown-ts-view-mode read-only mode with a single keynavigation: n, p, u, f, b, TAB. Good for reading a READMEwithout fear of typing into it.ExtrasEverything below lives in markdown-ts-mode-x.el, which is why weloaded it back in the setup.TOCA table of contents is delimited by HTML comments, so it survivesrendering anywhere:M-x markdown-ts-toc-insert-template inserts those markers, basicor complete (the complete one lists every parameter with itsdefault)M-x markdown-ts-toc-generate fills them in, and refills on everycallM-x markdown-ts-toc-clear empties,markdown-ts-toc-clear-and-remove also removes the markersM-x markdown-ts-toc-update-before-save-mode regenerates on saveParameters go inline in the opening comment: min-depth, max-depth,candidates, from, style, indent, no-link, relative-depth,ignore. A buffer can hold more than one table with differentparameters. Candidates are not only headings, list items, setextheaders and named code blocks can feed a table too.Raw:With markup hidden:ExportingM-x markdown-ts-convert converts the buffer,markdown-ts-convert-file a file. You get asked for the format andthe converter, unless you setmarkdown-ts-default-converter. Supported out of the box:PDF via pandocHTML via pandoc, cmark, cmark-gfm, markdown, markdown.plWith a prefix argument the result is displayed, by default with eww.See markdown-ts-convert-display-function to open in a browserinstead. That is your somewhat 'live' preview. Converting is not (yet)automatically when you make changes, maybe in the future.Example using eww, split manually made for this demo:Spec at handM-x markdown-ts-browse-commonmark-spec and M-x markdown-ts-browse-gfm-spec open the specs, for when you need tosettle an argument.Experiment with eglot and eldocThis is still experimental within the experimental, so don't blameeglot's author if something goes wrong. Send a bug report tomarkdown-ts-mode instead.If you set this:(setopt eglot-documentation-renderer #'markdown-ts-view-mode)Eglot will try to render documentation (usually Markdown provided bythe LSP server) using markdown-ts-mode.Again, we are still shaving off some rough edges here, and results mayvary. Please do help us test this, though.Play with optionsM-x customize-group RET markdown-ts RET and go through it. Some ofthe customs worth a look at first:markdown-ts for display: markup hiding, ellipsis, bullets,checkboxes, thematic break and hard line break characters, inlineimages, folding on opencode blocks: markdown-ts-code-block-modes,markdown-ts-default-code-block-mode,markdown-ts-enable-code-block-context-modetables: markdown-ts-enable-table-mode,markdown-ts-table-auto-align,markdown-ts-table-default-column-widthmarkdown-ts-convert for exportingmarkdown-ts-toc for tables of contentsFaces are customizable too, one per Markdown element.How you can helpThe best way you can help is simply by using it. Try it with yourMarkdown files, play with the different features, and see what needsimprovement or what breaks.If you find something that doesn't work as expected, please report itas a bug from Emacs itself with M-x report-emacs-bug RET. Include asmall example that reproduces the problem whenever possible. This isespecially useful for issues involving fontification, tree-sittergrammars, tables, code blocks, or interactions with other modes.We're still polishing the rough edges, so bug reports, feedback, andreal-world testing are very welcome.I found a bug, is it because markdown-ts-mode is buggy?Some of the surprises you may hit while using markdown-ts-mode mightbe the mode, some might be the grammar, some might come from howtree-sitter is integrated into Emacs, or from the tree-sitterecosystem as a whole. Knowing about this upfront helps understandingthat debugging is challenging.Grammars are a shared, external assetA grammar is not written for Emacs. The very sametree-sitter-markdown is consumed by other editors and tools, so anychange to it is negotiated among all of its users. That is great forthe ecosystem, and it also means a fix we would like to see may take awhile to land, or may never land in the shape we would prefer. Whenthat happens, we work around it inside the mode as best we can, andreport the issue upstream.So, if you find something that looks like a mode bug and the answerturns out to be "the grammar parses it this way", now you know wherethat answer comes from. Please do report it anyway, we would ratherhear about it twice than not at all.Building grammars has its own quirks too. Not every grammar buildswith make and a C compiler alone: several are generated from aJavaScript definition, so their build path expects the tree-sitterCLI, and sometimes a Node.js installation, to be available. This is agood part of why pre-compiled grammar bundles and distro packages areso popular. As said before, I still prefer compiling theminteractively from Emacs, but now you know why your distro may bepulling in more than you expected.Indirect buffersThis one deserves an explicit warning, because it surprises people:tree-sitter and indirect buffers do not get along.Parsers are not shared with indirect buffers. They belong to thebase buffer, and an indirect buffer starts with none. You eithercopy them over manually, or re-instantiate them by enabling a majormode in the indirect buffer.Font-lock in indirect buffers is not supported at all. This is alimitation in Emacs itself.The practical consequence is that (at least at the moment of thiswriting) if you use a package that clones a region into an indirectbuffer, expect no fontification there. This is not specific tomarkdown-ts-mode, it applies to every -ts-mode, and it is notsomething we can fix from the mode's side.Further readingIf this guide got you interested, there is a lot of good material outthere about writing and using tree-sitter modes. Stéphane Marks, mypartner in crime on this mode, put together the list below, and it istoo good to keep to ourselves. Some of it may be a little stale bynow, tree-sitter moves fast, but the reasoning in these articles holdsup:Pulsar Edit's tree-sitter series,another editor going through the same journeyBuilding Emacs major modes with tree-sitter: lessons learnedTree-sitter modes still need a syntax tableLet's write a tree-sitter major modeMaking an Emacs major mode for Cabal using tree-sitterAnd, of course, the notes from the people who built all of this intoEmacs, Yuan Fu and Juri Linkov, which are the closest thing we have toa canonical reference:Emacs 30 tree-sitter notesadmin/notes/tree-sitter in the Emacs repositoryIs this going to be out of the experimental tag on next Emacs release?In this post beginning I wrote:What this means? Should you use it or not? Is this ready? Is this just a sketch of a mode?Now you probably have a better answer.experimental does not mean markdown-ts-mode is just a sketch orthat it is missing the basic features you would expect from a Markdownmode. It means the mode is still evolving, and we are not yet readyto promise that its API, behavior, or some of its features won'tchange.So, should you use it? Yes! If you are comfortable with theexperimental label, please give it a try. The more people using itwith different Markdown files, configurations, and workflows, theeasier it is for us to find issues and fix it.Will it be out of experimental in the next Emacs release? Maybe, wesure are working towards it! We will see. There are still things topolish, limitations to work around, and feedback to process before wecan make that call.For now, consider this your invitation to play with it. And if youfind something weird, don't just work around it, let us know. That'show we get it ready.