Skip to main content
This configuration provides enhanced Markdown editing with live preview rendering, MDX support for React components, and grammar checking.

Live preview

The render-markdown.nvim plugin renders Markdown in the buffer:
-- From lua/plugins/languages/markdown.lua:2
{
  "MeanderingProgrammer/render-markdown.nvim",
  ft = { "markdown", "mdx" },
  dependencies = { "nvim-treesitter/nvim-treesitter", "nvim-mini/mini.nvim" },
  opts = {
    file_types = { "markdown", "mdx" },
    html = {
      render_modes = true,
    },
  },
}

Features

Headings

Different styles and icons for each heading level

Code blocks

Syntax highlighting within fenced code blocks

Lists

Rendered bullet points and checkboxes

Tables

Properly aligned table rendering

Links

Concealed URLs with descriptive text

Emphasis

Bold, italic, and strikethrough rendering

Treesitter integration

-- From lua/plugins/languages/markdown.lua:13
pcall(vim.treesitter.language.register, "markdown", "mdx")
MDX files use the markdown Treesitter parser for consistent highlighting.

MDX support

MDX (Markdown + JSX) is fully supported:
-- From lua/plugins/languages/markdown.lua:19
{
  "davidmh/mdx.nvim",
  lazy = false,
  dependencies = { "nvim-treesitter/nvim-treesitter" },
}
The mdx.nvim plugin must be loaded early (not lazy) so .mdx files are detected correctly.

What is MDX?

MDX allows you to use JSX components in Markdown:
# My Document

This is regular markdown.

<CustomComponent prop="value">
  With JSX components!
</CustomComponent>

## More markdown

Back to regular markdown.
Popular in:
  • Documentation sites (Docusaurus, Nextra)
  • Blogs with interactive elements
  • Component documentation

Language server

Markdown Oxide LSP is available but currently disabled:
-- From lua/plugins/lsp/init.lua:53
-- TODO: Re-enable markdown_oxide when needed
-- "markdown_oxide", -- Markdown
The configuration is preserved in after/lsp/markdown_oxide.lua for future use.

markdown_oxide features

When enabled, it provides:
  • Daily note navigation
  • Wiki-style linking
  • Code lens for references
  • Vault-wide search
Installation:
cargo install --locked markdown-oxide

Grammar and spell checking

The harper_ls language server provides grammar checking:
-- From lua/plugins/lsp/init.lua:59
vim.lsp.enable({
  "harper_ls", -- Grammar/spell checking for markdown, gitcommit, etc.
})

What harper_ls checks

  • Grammar errors
  • Spelling mistakes
  • Style suggestions
  • Common writing issues

Supported filetypes

  • markdown
  • mdx
  • gitcommit
  • Plain text files
Installation:
cargo install --locked harper-ls

Formatting

Markdown files are formatted with markdownlint-cli2:
-- From lua/plugins/formatter.lua:68
markdown = { "markdownlint-cli2", stop_after_first = true }
Format-on-save is enabled by default. Installation:
npm install -g markdownlint-cli2

markdownlint configuration

Create a .markdownlint.jsonc file in your project root:
{
  "default": true,
  "MD013": false,  // Disable line length rule
  "MD033": false,  // Allow inline HTML
  "MD041": false   // Don't require first line to be h1
}

Linting

Markdown linting also uses markdownlint-cli2:
-- From lua/plugins/linting.lua:69
markdown = { "markdownlint-cli2" }
Trigger linting with <leader>ll.
The same tool is used for both formatting and linting. Formatting fixes issues automatically, while linting reports them.

Common workflows

Live preview

  1. Open a Markdown file
  2. Preview renders automatically
  3. Edit and see changes in real-time

Working with headings

Headings render with different styles:
  • # renders largest
  • ## slightly smaller
  • Continue through ######

Code blocks

```lua
local x = 42
```
Syntax highlighting works for:
  • lua, python, javascript, typescript
  • go, rust, c, cpp
  • bash, sh, zsh
  • json, yaml, toml
  • And many more via Treesitter

Tables

| Column 1 | Column 2 |
|----------|----------|
| Value 1  | Value 2  |
Tables render with proper alignment.

Lists and checkboxes

- Bullet point
- Another point
  - Nested point

- [ ] Unchecked task
- [x] Completed task
Checkboxes render as actual checkboxes.
[Link text](https://example.com)
[Reference link][ref]

[ref]: https://example.com "Title"
URLs are concealed, showing only link text.

MDX-specific features

Component imports

import { CustomComponent } from './components'

# Document

<CustomComponent />
Autocompletion works for imported components (if you have TypeScript LSP configured).

Props and attributes

<Component
  prop1="value"
  prop2={variable}
  onClick={() => console.log('clicked')}
/>
JSX syntax highlighting and validation.

Mixing markdown and JSX

## Markdown Section

Regular markdown text.

<div className="custom">
  
  ### More Markdown
  
  Inside JSX!
  
</div>

Keymaps

Standard LSP keymaps (when harper_ls or markdown_oxide is active):
KeymapAction
KHover documentation
gaCode action (fix grammar/spelling)
<leader>fbFormat with markdownlint
<leader>llLint file
]d / [dNext/prev diagnostic

Installation summary

1

Install markdownlint

npm install -g markdownlint-cli2
2

Install harper_ls

cargo install --locked harper-ls
3

Optional: Install markdown_oxide

cargo install --locked markdown-oxide
Then enable it in lua/plugins/lsp/init.lua:53.
4

Verify installation

markdownlint-cli2 --version
harper-ls --version

Troubleshooting

Preview not rendering

  1. Check Treesitter is installed: :TSInstall markdown
  2. Verify render-markdown is loaded: :Lazy
  3. Try toggling render: :RenderMarkdown toggle

MDX not recognized

  1. Verify mdx.nvim is loaded: :Lazy
  2. Check filetype: :set filetype? (should be mdx)
  3. Restart Neovim

Grammar checking not working

  1. Check harper_ls is installed: harper-ls --version
  2. Run :LspInfoCustom to see if it’s attached
  3. Verify filetype is supported
  4. Check logs: :LspLog

Formatting not working

  1. Check markdownlint is installed: markdownlint-cli2 --version
  2. Run :ConformInfo
  3. Manually format: <leader>fb
  4. Check for .markdownlint.jsonc conflicts

Advanced configuration

Custom render-markdown settings

Edit lua/plugins/languages/markdown.lua:6 to customize rendering:
opts = {
  file_types = { "markdown", "mdx" },
  html = { render_modes = true },
  -- Add custom settings:
  headings = { "󰲡 ", "󰲣 ", "󰲥 ", "󰲧 ", "󰲩 ", "󰲫 " },
  code = { enabled = true },
  checkbox = {
    checked = { icon = '✔ ' },
    unchecked = { icon = '✘ ' },
  },
}

Enable markdown_oxide

  1. Uncomment in lua/plugins/lsp/init.lua:53:
    "markdown_oxide",
    
  2. Uncomment config in after/lsp/markdown_oxide.lua
  3. Restart Neovim

Custom markdownlint rules

Create .markdownlint.jsonc:
{
  "default": true,
  "MD003": { "style": "atx" },  // Heading style
  "MD007": { "indent": 2 },     // List indentation
  "MD013": false,               // Line length
  "MD024": false,               // Duplicate headings
  "MD033": false,               // Inline HTML
  "MD041": false                // First line heading
}

Next steps

TypeScript

For MDX with TypeScript components

Formatting

Learn more about formatters

Linting

Learn more about linters

LSP setup

Configure language servers

Build docs developers (and LLMs) love