A lightweight and highly customizable Markdown rendering and live-preview plugin for Neovim.
fk_markdown.nvim brings two complementary experiences together:
The rendering system is designed to be highly configurable, allowing individual Markdown elements to be customized through Lua.
{
'the-mayankjha/fk_markdown.nvim',
config = function()
require('fk_markdown').setup({})
end,
}
use {
'the-mayankjha/fk_markdown.nvim',
config = function()
require('fk_markdown').setup({})
end,
}
Plug 'the-mayankjha/fk_markdown.nvim'
Then add:
require('fk_markdown').setup({})
:Rocks install fk_markdown.nvim
[!NOTE] This Plugin is tested for lazy.nvim only
fk_markdown.nvim works out of the box with a minimal configuration:
require('fk_markdown').setup({})
For most users, this is all that is required to get started.
The rest of this README is divided into two major sections:
The rendering system transforms Markdown syntax into a cleaner and more expressive representation directly inside Neovim.
Each Markdown component can be configured independently, allowing you to control icons, colors, borders, padding, backgrounds, and rendering behavior.
The rendering system was initially inspired by and built with reference to the excellent render-markdown.nvim project by MeanderingProgrammer. The project provides a strong foundation for in-editor Markdown rendering and demonstrates a highly configurable component-based rendering architecture. render-markdown.nvim
| Component | Features |
|---|---|
| Headings | Icons, colors, backgrounds, per-level styling |
| Code Blocks | Borders, language icons, titles, backgrounds, padding |
| Tables | Borders, presets, padding, alignment indicators |
| Callouts | Boxed and compact styles, icons, colors |
| Blockquotes | Accent bars, icons, nested highlight groups |
| Bullets | Custom icons, nesting-aware rendering, padding |
| Checkboxes | Custom checked/unchecked icons and states |
| Links | Context-aware icons, WikiLinks, custom destinations |
| Thematic Breaks | Custom icons, width, and highlighting |
| Images | Styled image indicators |
| Inline Elements | Custom highlighting and rendering behavior |
Render Markdown headings (# through ######) using custom icons and per-level styling.
require('fk_markdown').setup({
heading = {
enabled = true,
icon = true,
icons = {
' ', ' ', ' ',
' ', ' ', ' ',
},
background = {
enabled = false,
bg_color = {
"#1e1e2e", "#1e1e2e", "#1e1e2e",
"#1e1e2e", "#1e1e2e", "#1e1e2e",
},
font_color = {
"#f38ba8", "#fab387", "#f9e2af",
"#a6e3a1", "#74c7ec", "#cba6f7",
},
},
},
})
enabled — Enable or disable heading rendering.icon — Use custom icons instead of native # markers.icons — Icons used for H1 through H6.background.enabled — Enable or disable heading backgrounds.background.bg_color — Background colors for H1 through H6.background.font_color — Foreground colors for H1 through H6.Render fenced code blocks with configurable borders, language indicators, titles, backgrounds, and padding.
require('fk_markdown').setup({
code = {
enabled = true,
-- 'wide' or 'compact'
style = 'wide',
background = {
enabled = false,
color = "#181825",
},
padding = {
top = 1,
bottom = 1,
left = 1,
right = 2,
},
border = {
enabled = true,
type = "dynamic",
color = "#f38ba8",
},
title = {
enabled = true,
type = "dynamic",
color = "#a6e3a1",
},
icon = {
enabled = true,
},
},
})
style — 'wide' or 'compact'.background.enabled / background.color — Configure the block background.padding — Configure top, bottom, left, and right spacing.border.type — "dynamic" or "static".title.enabled — Enable or disable language title pills.title.type — Use dynamic or static title colors.icon.enabled — Enable or disable language icons.Render Markdown pipe tables with configurable borders, cell padding, presets, and alignment indicators.
require('fk_markdown').setup({
pipe_table = {
enabled = true,
-- 'round' | 'double' | 'heavy' | 'none'
preset = 'none',
-- 'padded' | 'trimmed' | 'raw' | 'overlay'
cell = 'padded',
padding = 1,
min_width = 0,
border = {
'┌', '┬', '┐',
'├', '┼', '┤',
'└', '┴', '┘',
'│', '─',
},
border_enabled = true,
alignment_indicator = '━',
head = 'RenderMarkdownTableHead',
row = 'RenderMarkdownTableRow',
-- 'full' | 'normal' | 'none'
style = 'full',
},
})
preset — 'round', 'double', 'heavy', or 'none'.cell — 'padded', 'trimmed', 'raw', or 'overlay'.padding — Cell spacing.border — Custom border characters.border_enabled — Enable or disable table borders.alignment_indicator — Character used to indicate alignment.style — 'full', 'normal', or 'none'.Render GitHub- and Obsidian-style callouts with custom icons, colors, backgrounds, borders, and layouts.
require('fk_markdown').setup({
quote = {
enabled = true,
-- 'boxy' or 'compact'
style = 'boxy',
border = true,
bg = "NONE",
fg = "#cad3f5",
},
})
style — 'boxy' or 'compact'.border — Enable or disable the container border.bg — Background color.fg — Foreground color.Blockquotes can be rendered using a custom accent bar and level-based highlights.
require('fk_markdown').setup({
quote = {
enabled = true,
icon = '▋',
repeat_linebreak = false,
highlight = {
'RenderMarkdownQuote1',
'RenderMarkdownQuote2',
'RenderMarkdownQuote3',
'RenderMarkdownQuote4',
'RenderMarkdownQuote5',
'RenderMarkdownQuote6',
},
},
})
Customize list bullets according to nesting depth.
require('fk_markdown').setup({
bullet = {
enabled = true,
icons = {
'●', '○', '◆', '◇',
},
left_pad = 0,
right_pad = 0,
highlight = 'RenderMarkdownBullet',
},
})
icons — Bullet characters cycled by nesting depth.left_pad / right_pad — Horizontal spacing.highlight — Highlight group for bullet icons.Replace Markdown task-list markers with custom icons and highlight groups.
require('fk_markdown').setup({
checkbox = {
enabled = true,
unchecked = {
icon = ' ',
highlight = 'RenderMarkdownUnchecked',
},
checked = {
icon = ' ',
highlight = 'RenderMarkdownChecked',
},
custom = {
todo = {
raw = '[-]',
rendered = ' ',
highlight = 'RenderMarkdownTodo',
},
},
},
})
Render links with contextual icons based on their destination.
Supports:
require('fk_markdown').setup({
link = {
enabled = true,
image = ' ',
email = ' ',
hyperlink = ' ',
highlight = 'RenderMarkdownLink',
wiki = {
enabled = true,
icon = ' ',
},
custom = {
web = {
icon = ' ',
pattern = '^http',
},
github = {
icon = ' ',
pattern = 'github%.com',
kind = 'url',
},
youtube = {
icon = ' ',
pattern = 'youtube[^.]*%.com',
kind = 'url',
},
},
},
})
Customize horizontal rules and Markdown thematic breaks.
require('fk_markdown').setup({
dash = {
enabled = true,
icon = '─',
width = 'full',
highlight = 'RenderMarkdownDash',
},
})
A complete example combining the major rendering components:
require('fk_markdown').setup({
-- ── Headings ──────────────────────────────────────────────
heading = {
enabled = true,
icon = true,
icons = {
' ', ' ', ' ',
' ', ' ', ' ',
},
background = {
enabled = false,
bg_color = {
"#1e1e2e", "#1e1e2e", "#1e1e2e",
"#1e1e2e", "#1e1e2e", "#1e1e2e",
},
font_color = {
"#f38ba8", "#fab387", "#f9e2af",
"#a6e3a1", "#74c7ec", "#cba6f7",
},
},
},
-- ── Code Blocks ───────────────────────────────────────────
code = {
enabled = true,
style = 'wide',
background = {
enabled = false,
color = "#181825",
},
padding = {
top = 1,
bottom = 1,
left = 1,
right = 2,
},
border = {
enabled = true,
type = "dynamic",
color = "#f38ba8",
},
title = {
enabled = true,
type = "dynamic",
color = "#a6e3a1",
},
icon = {
enabled = true,
},
},
-- ── Callouts ──────────────────────────────────────────────
quote = {
enabled = true,
style = 'boxy',
border = true,
bg = "NONE",
fg = "#cad3f5",
},
-- ── Bullets ───────────────────────────────────────────────
bullet = {
enabled = true,
icons = { '●', '○', '◆', '◇' },
},
-- ── Checkboxes ────────────────────────────────────────────
checkbox = {
enabled = true,
unchecked = {
icon = ' ',
highlight = 'RenderMarkdownUnchecked',
},
checked = {
icon = ' ',
highlight = 'RenderMarkdownChecked',
},
},
-- ── Tables ────────────────────────────────────────────────
pipe_table = {
enabled = true,
preset = 'none',
style = 'full',
},
-- ── Links ─────────────────────────────────────────────────
link = {
enabled = true,
image = ' ',
hyperlink = ' ',
},
-- ── Thematic Breaks ───────────────────────────────────────
dash = {
enabled = true,
icon = '─',
},
-- ── Signs ─────────────────────────────────────────────────
sign = {
enabled = true,
},
-- ── Indentation ───────────────────────────────────────────
indent = {
enabled = false,
},
})
Detailed configuration documentation:
The Preview system is a separate part of fk_markdown.nvim.
Instead of rendering Markdown inside Neovim, Preview runs a lightweight local web server and opens the current Markdown document in your browser.
Changes made in Neovim are automatically synchronized with the browser.
Changes to the Markdown document are automatically pushed to the browser without manually refreshing the page.
The browser can follow the current cursor position in Neovim.
Moving through the document in Neovim automatically updates the browser's scroll position.
Open a rendered Markdown document in your preferred browser directly from Neovim.
Local images are resolved relative to the Markdown document and served through the preview server.
Code blocks are rendered using Highlight.js with support for predefined themes and custom color overrides.
Preview supports:
$...$$$...$$math fenced code blockslatex fenced code blocksMath rendering is provided through KaTeX.
PlantUML code blocks can be rendered as responsive SVG/PNG diagrams.
Example:
```plantuml
@startuml
Alice -> Bob: Hello
Bob --> Alice: Hi!
@enduml
```
The preview renderer supports GitHub-style alerts:
[!NOTE] This is a note.
[!TIP] This is a tip.
[!IMPORTANT] This is important.
[!WARNING] This is a warning.
[!CAUTION] This is a caution.
Images are rendered responsively inside the browser preview.
The preview server uses Server-Sent Events to push document updates to the browser.
The preview server uses Neovim's built-in networking capabilities through vim.uv / vim.loop, avoiding the need for an external web server.
| Command | Description |
|---|---|
:FkPreview |
Start the preview server and open the browser |
:FkPreviewStop |
Stop the preview server |
:FkPreviewToggle |
Toggle the preview server |
:FkPreviewAutoScroll [on|off|toggle] |
Enable, disable, or toggle synchronized scrolling |
require('fk_markdown').setup({
preview = {
enabled = true,
-- Automatically start preview
auto_start = false,
-- Automatically stop preview when the buffer closes
auto_close = true,
-- Synchronize browser scrolling with the Neovim cursor
auto_scroll = true,
-- Browser executable.
-- Empty string uses the system default.
browser = "",
-- Address used by the preview server
open_ip = "127.0.0.1",
-- nil = automatically select a random available port
port = nil,
-- "dark" or "light"
theme = "dark",
-- ── Syntax Highlighting ────────────────────────────────
syntax_highlight = {
enabled = true,
-- Highlight.js theme
theme = "github-dark",
-- Available examples:
-- "atom-one-dark"
-- "monokai"
-- "tokyo-night-dark"
-- "dracula"
colors = {
background = "#181825",
keyword = "#cba6f7",
string = "#a6e3a1",
comment = "#6c7086",
function_name = "#89b4fa",
},
},
-- ── Keymaps ────────────────────────────────────────────
keymap = {
start = "<leader>mp",
stop = "<leader>ms",
toggle = "<leader>mt",
},
},
})
The preview system follows a simple workflow:
┌────────────────────┐
│ Markdown Buffer │
│ Neovim │
└─────────┬──────────┘
│
│ File Changes
▼
┌────────────────────┐
│ Local HTTP Server │
│ vim.uv / loop │
└─────────┬──────────┘
│
│ SSE / HTTP
▼
┌────────────────────┐
│ Browser Preview │
│ │
│ Markdown → HTML │
│ Highlight.js │
│ KaTeX │
│ PlantUML │
└────────────────────┘
This keeps the preview system local and lightweight while providing a much richer viewing experience than terminal-only rendering.
The preview supports both light and dark themes:
preview = {
theme = "dark",
}
Syntax highlighting can independently be configured using Highlight.js themes:
syntax_highlight = {
enabled = true,
theme = "github-dark",
}
Custom colors can be supplied when additional control is required:
syntax_highlight = {
colors = {
background = "#181825",
keyword = "#cba6f7",
string = "#a6e3a1",
comment = "#6c7086",
function_name = "#89b4fa",
},
}
For detailed preview configuration, see:
fk_markdown.nvim intentionally separates the two systems:
| Rendering | Preview | |
|---|---|---|
| Environment | Neovim | Web browser |
| Purpose | Better editing experience | Final document preview |
| Rendering | Virtual text / extmarks | HTML |
| Live Updates | Immediate | Live reload |
| Scrolling | Native editor | Synchronized with cursor |
| Syntax Highlighting | Neovim | Highlight.js |
| Math | Editor rendering | KaTeX |
| PlantUML | Editor support | SVG/PNG diagrams |
| Images | Editor rendering | Local image serving |
| Callouts | Neovim rendering | HTML/CSS rendering |
The two systems complement each other rather than replacing one another.
fk_markdown.nvim would not exist without the work and ideas shared by the Neovim community.
render-markdown.nvimSpecial thanks to MeanderingProgrammer for render-markdown.nvim.
The initial idea for fk_markdown.nvim, as well as the foundation and general approach to in-editor Markdown rendering, was heavily inspired by and developed with reference to render-markdown.nvim.
Its component-based rendering architecture and approach to making Markdown more pleasant to read inside Neovim provided an important starting point for this project.
This project is independently developed and extends that idea with additional customization and a separate browser-based Preview system.
Thanks to the developers and maintainers of the many open-source projects that make this plugin possible and provide valuable references, inspiration, tooling, and integrations.
In particular:
We are grateful to the maintainers and contributors of these projects and to the broader Neovim ecosystem.
Contributions are welcome!
Before submitting a significant change:
fk_markdown.nvim is licensed under the MIT License.
See LICENSE for the complete license text.
Built for the Neovim community.
If you find fk_markdown.nvim useful:
Thank you to everyone who uses, contributes to, and helps improve the project.