← Back to Tutorials
JavaScript

Build a Markdown Editor in JavaScript

Difficulty: Intermediate Est. Time: ~2 hours

Introduction

Markdown editors are essential tools for developers, writers, and content creators. They combine the simplicity of plain text with powerful formatting capabilities.

In this tutorial, you'll build a feature-rich Markdown editor with live preview, toolbar buttons, and local storage support.

What You'll Build
  • Split-pane editor (write/preview)
  • Live Markdown rendering
  • Formatting toolbar
  • Auto-save to local storage
  • Export functionality
What You'll Learn
  • Markdown parsing with marked.js
  • DOM manipulation
  • Local storage API
  • Real-time preview
  • Event handling

What is Markdown?

Markdown is a lightweight markup language that allows you to format text using simple syntax.

Common Markdown Syntax

# Heading 1
## Heading 2

**Bold text**
*Italic text*

- Bullet point
- Another point

[Link text](https://example.com)

```
Code block
```

> Blockquote
Why Markdown?
  • Easy to learn and read
  • Converts to clean HTML
  • Used by GitHub, Reddit, Stack Overflow
  • Perfect for documentation

Project Overview

Our Markdown editor will feature:

  • Split-screen layout (editor | preview)
  • Real-time Markdown conversion
  • Toolbar with common formatting buttons
  • Auto-save functionality
  • Export to HTML/Markdown file

Project Setup

Bash
# Create project folder
mkdir markdown-editor
cd markdown-editor

# Create files
touch index.html styles.css app.js

We'll use a CDN for the Markdown library:

  • marked.js - Converts Markdown to HTML
  • highlight.js - Code syntax highlighting (optional)

HTML Structure

Let's build the editor interface.

HTML
<!-- 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>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="editor-container">
        <!-- Toolbar -->
        <div class="toolbar">
            <button data-action="bold" title="Bold">B</button>
            <button data-action="italic" title="Italic">I</button>
            <button data-action="heading" title="Heading">H</button>
            <button data-action="link" title="Link">🔗</button>
            <button data-action="code" title="Code"><></button>
            <button data-action="quote" title="Quote">"</button>
            <button data-action="list" title="List">☰</button>
            <div class="toolbar-spacer"></div>
            <button data-action="download-md" title="Download MD">↓ MD</button>
            <button data-action="download-html" title="Download HTML">↓ HTML</button>
        </div>

        <!-- Editor Area -->
        <div class="editor-pane">
            <textarea id="editor" placeholder="Type your markdown here..."># Welcome to Markdown Editor

Start typing to see your **formatted** text appear in real-time!

## Features
- Live preview
- Toolbar buttons
- Auto-save

```javascript
console.log('Code highlighting!');
```</textarea>
        </div>

        <!-- Preview Area -->
        <div class="preview-pane">
            <div class="preview-label">Preview</div>
            <div id="preview"></div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <script src="app.js"></script>
</body>
</html>

Markdown Parsing

Now let's add the JavaScript to convert Markdown to HTML.

JavaScript
// app.js

// DOM Elements
const editor = document.getElementById('editor');
const preview = document.getElementById('preview');

// Configure marked.js
marked.setOptions({
    breaks: true,      // Convert \n to <br>
    gfm: true         // GitHub Flavored Markdown
});

// Convert markdown to HTML
function updatePreview() {
    const markdown = editor.value;
    const html = marked.parse(markdown);
    preview.innerHTML = html;
}

// Event listener for input
editor.addEventListener('input', updatePreview);

// Initial render
updatePreview();
marked.js Features
  • GFM: Tables, task lists, strikethrough
  • Sanitize: Can filter HTML for security
  • Custom rendering: Override element rendering

Live Preview Enhancement

Let's make the preview update smoothly with scroll sync.

JavaScript
// Live preview with debouncing
let debounceTimer;

function debounce(func, wait) {
    return function(...args) {
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => func.apply(this, args), wait);
    };
}

// Update preview with debounce (300ms delay)
const debouncedUpdate = debounce(updatePreview, 300);

editor.addEventListener('input', debouncedUpdate);

// Syntax highlighting for code blocks
function highlightCode() {
    preview.querySelectorAll('pre code').forEach((block) => {
        block.classList.add('language-javascript');
        // In production, use highlight.js here
    });
}

// Add after updatePreview
function updatePreview() {
    const markdown = editor.value;
    const html = marked.parse(markdown);
    preview.innerHTML = html;
    highlightCode();
}

Adding Toolbar

Let's make the toolbar buttons insert Markdown syntax.

JavaScript
// Toolbar functionality
const toolbarButtons = document.querySelectorAll('[data-action]');

// Insert text at cursor position
function insertText(before, after = '') {
    const start = editor.selectionStart;
    const end = editor.selectionEnd;
    const selectedText = editor.value.substring(start, end);
    
    // Build new text
    const newText = before + selectedText + after;
    
    // Replace selection
    editor.value = 
        editor.value.substring(0, start) + 
        newText + 
        editor.value.substring(end);
    
    // Move cursor
    editor.selectionStart = start + before.length;
    editor.selectionEnd = start + before.length + selectedText.length;
    editor.focus();
    
    // Update preview
    updatePreview();
}

// Toolbar button handlers
toolbarButtons.forEach(btn => {
    btn.addEventListener('click', () => {
        const action = btn.dataset.action;
        
        switch(action) {
            case 'bold':
                insertText('**', '**');
                break;
            case 'italic':
                insertText('*', '*');
                break;
            case 'heading':
                insertText('## ');
                break;
            case 'link':
                insertText('[', '](url)');
                break;
            case 'code':
                insertText('`', '`');
                break;
            case 'quote':
                insertText('> ');
                break;
            case 'list':
                insertText('- ');
                break;
            case 'download-md':
                downloadFile('document.md', editor.value, 'text/markdown');
                break;
            case 'download-html':
                const htmlContent = `<!DOCTYPE html>
<html>
<head><title>Document</title>
<body>${marked.parse(editor.value)}</body>
</html>`;
                downloadFile('document.html', htmlContent, 'text/html');
                break;
        }
    });
});

// Download helper
function downloadFile(filename, content, type) {
    const blob = new Blob([content], { type });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url);
}

Local Storage

Let's add auto-save functionality.

JavaScript
// Auto-save to local storage
const STORAGE_KEY = 'markdown-editor-content';

// Save content
function saveContent() {
    localStorage.setItem(STORAGE_KEY, editor.value);
}

// Load saved content
function loadContent() {
    const saved = localStorage.getItem(STORAGE_KEY);
    if (saved) {
        editor.value = saved;
        updatePreview();
    }
}

// Save on input (debounced)
editor.addEventListener('input', debounce(saveContent, 1000));

// Load on page load
loadContent();

// Optional: Clear saved content
// localStorage.removeItem(STORAGE_KEY);

Summary

Congratulations! You've built a complete Markdown editor.

What You Built

  • Split-pane Editor - Write and preview side by side
  • Live Preview - Real-time Markdown to HTML conversion
  • Toolbar - Quick formatting buttons
  • Auto-save - Local storage persistence
  • Export - Download as MD or HTML

Key Concepts Learned

  • Markdown parsing with marked.js
  • Text manipulation in textareas
  • Debouncing for performance
  • Local storage API
  • Blob and download API

Enhancements to Try

  • Add syntax highlighting with highlight.js
  • Implement image upload
  • Add keyboard shortcuts
  • Create dark/light themes
  • Add table support

Continue Learning

Try these tutorials:

  • Build a Task Manager
  • Build a Weather Dashboard
  • Build a Web Scraper