Build a Markdown Editor with Live Preview (Step by Step)
In this tutorial, you'll learn about Build a Markdown Editor with Live Preview (Step by Step). We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Build a browser-based Markdown editor with a live preview pane, syntax-highlighted code blocks, and file save/export — using vanilla HTML, CSS, and JavaScript with the marked library.
What You'll Build
You'll build a split-pane Markdown editor: type Markdown on the left, see the rendered HTML on the right — instantly. It includes syntax highlighting for code blocks, a toolbar for quick formatting, and buttons to save as .md, export as .html, or clear the document. At DodaTech, this same pattern is used in DodaZIP's release note editor and in the documentation preview tool.
Why Build a Markdown Editor?
Markdown is everywhere — README files, documentation, forum posts, note-taking apps. Building your own editor teaches you DOM manipulation, event handling, third-party library integration, file I/O via the browser's File API, and the concept of "live preview" that's central to modern development tools. Plus, you end up with a tool you can actually use daily.
Prerequisites
- Basic HTML, CSS, and JavaScript
- Familiarity with Markdown syntax
- No backend needed — everything runs in the browser
Step 1: Setup
mkdir md-editor
cd md-editor
Create a single HTML file that contains everything:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Markdown Editor</title>
<!-- marked: Markdown → HTML conversion -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- highlight.js: syntax highlighting for code blocks -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/github.css">
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/highlight.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; height: 100vh; display: flex; flex-direction: column; }
/* Toolbar */
.toolbar {
display: flex; gap: 4px; padding: 8px 12px;
background: #f8f9fa; border-bottom: 1px solid #dee2e6;
align-items: center; flex-wrap: wrap;
}
.toolbar button {
padding: 6px 12px; border: 1px solid #dee2e6; border-radius: 4px;
background: white; cursor: pointer; font-size: 13px;
transition: background 0.15s;
}
.toolbar button:hover { background: #e9ecef; }
.toolbar .divider { width: 1px; height: 24px; background: #dee2e6; margin: 0 4px; }
.toolbar .spacer { flex: 1; }
.toolbar .filename { font-size: 13px; color: #666; }
.toolbar .word-count { font-size: 12px; color: #999; }
/* Main editor area */
.editor-container {
display: flex; flex: 1; overflow: hidden;
}
.pane {
flex: 1; display: flex; flex-direction: column; overflow: hidden;
}
.pane-header {
padding: 6px 12px; font-size: 12px; font-weight: 600;
color: #666; background: #f1f3f5; border-bottom: 1px solid #dee2e6;
text-transform: uppercase; letter-spacing: 0.5px;
}
.pane:first-child { border-right: 1px solid #dee2e6; }
/* Editor textarea */
#editor {
flex: 1; padding: 16px; border: none; outline: none;
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
font-size: 14px; line-height: 1.7; resize: none;
tab-size: 4;
}
/* Preview pane */
#preview {
flex: 1; padding: 16px; overflow-y: auto;
font-size: 15px; line-height: 1.7;
}
/* Preview styles */
#preview h1 { font-size: 2em; margin: 0.5em 0 0.3em; border-bottom: 2px solid #eee; padding-bottom: 0.2em; }
#preview h2 { font-size: 1.6em; margin: 0.5em 0 0.3em; border-bottom: 1px solid #eee; padding-bottom: 0.15em; }
#preview h3 { font-size: 1.3em; margin: 0.4em 0 0.2em; }
#preview p { margin: 0.5em 0; }
#preview ul, #preview ol { margin: 0.5em 0; padding-left: 2em; }
#preview li { margin: 0.25em 0; }
#preview blockquote {
margin: 0.5em 0; padding: 8px 16px; border-left: 4px solid #007bff;
background: #f8f9fa; color: #555;
}
#preview code {
background: #f1f3f5; padding: 2px 6px; border-radius: 3px;
font-family: monospace; font-size: 0.9em;
}
#preview pre {
margin: 0.5em 0; border-radius: 6px; overflow-x: auto;
}
#preview pre code {
background: none; padding: 0; font-size: 0.85em;
}
#preview table { border-collapse: collapse; margin: 0.5em 0; width: 100%; }
#preview th, #preview td { border: 1px solid #dee2e6; padding: 8px 12px; text-align: left; }
#preview th { background: #f8f9fa; font-weight: 600; }
#preview img { max-width: 100%; border-radius: 4px; }
#preview a { color: #007bff; }
#preview hr { margin: 1em 0; border: none; border-top: 1px solid #dee2e6; }
/* Scrollbar styling */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: #f1f1f1; }
::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #aaa; }
</style>
</head>
<body>
Step 2: The Toolbar
<body>
<div class="toolbar">
<button onclick="insertFormat('**','**')" title="Bold">B</button>
<button onclick="insertFormat('*','*')" title="Italic"><em>I</em></button>
<button onclick="insertFormat('`','`')" title="Code"></></button>
<button onclick="insertFormat('[','](url)')" title="Link">🔗</button>
<button onclick="insertFormat('')" title="Image">🖼</button>
<div class="divider"></div>
<button onclick="insertLine('# ')">H1</button>
<button onclick="insertLine('## ')">H2</button>
<button onclick="insertLine('### ')">H3</button>
<div class="divider"></div>
<button onclick="insertLine('- ')">• List</button>
<button onclick="insertLine('1. ')">1. List</button>
<button onclick="insertLine('> ')">Quote</button>
<button onclick="insertLine('```\n\n```')">Code Block</button>
<div class="divider"></div>
<button onclick="insertLine('---')">HR</button>
<div class="spacer"></div>
<span class="filename" id="filename">untitled.md</span>
<span class="word-count" id="wordCount">0 words</span>
<div class="divider"></div>
<button onclick="saveMarkdown()">💾 Save .md</button>
<button onclick="exportHtml()">📄 Export HTML</button>
<button onclick="clearEditor()">🗑 Clear</button>
</div>
<div class="editor-container">
<div class="pane">
<div class="pane-header">Editor</div>
<textarea id="editor" placeholder="Type Markdown here...
# Heading 1
## Heading 2
**Bold** and *italic* text
- List item 1
- List item 2
```python
print('Hello, World!')
```"># Welcome to Markdown Editor
## Features
- **Live preview** as you type
- **Syntax highlighting** for code blocks
- **Toolbar** for quick formatting
- **Export** as HTML or Markdown
## Code Example
```python
def greet(name):
return f"Hello, {name}!"
print(greet("Markdown"))
Table
| Feature | Status |
|---|---|
| Live Preview | ✅ |
| Syntax Highlighting | ✅ |
| File Export | ✅ |