Freedom Sale
Independence Day Special — Unlock the AI Path 70% off our most popular AI course · Limited time offer
--Days
--Hrs
--Min
--Sec
Claim Your Discount
✦ Beginner ⏱ 25 min

📝 Build a Markdown Previewer with JavaScript

🎯 What You'll Build

A live Markdown previewer with a split-pane editor — type on the left, see rendered HTML instantly on the right. Built with a custom Markdown parser in pure JavaScript.

📋 What You'll Need

1

Write a simple Markdown parser

Convert the most common Markdown syntax to HTML using regex replacements.

function parseMarkdown(md) {
  let html = md
    // Escape HTML (security: prevent XSS from user input)
    .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')

    // Headers (process from h3 down to h1 to avoid conflicts)
    .replace(/^### (.+)$/gm, '<h3>$1</h3>')
    .replace(/^## (.+)$/gm,  '<h2>$1</h2>')
    .replace(/^# (.+)$/gm,   '<h1>$1</h1>')

    // Code blocks (triple backticks)
    .replace(/```([\s\S]*?)```/gm, '<pre><code>$1</code></pre>')

    // Inline code
    .replace(/`([^`]+)`/g, '<code>$1</code>')

    // Bold + italic
    .replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>')
    .replace(/\*\*(.+?)\*\*/g,     '<strong>$1</strong>')
    .replace(/\*(.+?)\*/g,         '<em>$1</em>')

    // Links and images
    .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%">')
    .replace(/\[([^\]]+)\]\(([^)]+)\)/g,  '<a href="$2" target="_blank">$1</a>')

    // Blockquotes
    .replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')

    // Unordered lists (naive)
    .replace(/^\- (.+)$/gm, '<li>$1</li>')
    .replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')

    // Paragraphs (double newlines)
    .replace(/\n\n+/g, '</p><p>')
    .replace(/\n/g, '<br>');

  return '<p>' + html + '</p>';
}

// Test
console.log(parseMarkdown('# Hello\n\nThis is **bold** and *italic* text.'));
2

Split-pane editor with live preview

A textarea on the left updates the rendered HTML on the right with every keystroke.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Markdown Previewer</title>
  <style>
    * { margin:0; padding:0; box-sizing:border-box; }
    body { font-family:'Segoe UI',sans-serif; background:#0f172a; color:#f8fafc; height:100vh; display:flex; flex-direction:column; }
    header { padding:14px 20px; background:#1e293b; border-bottom:1px solid #334155; display:flex; justify-content:space-between; align-items:center; }
    header h1 { font-size:1.1rem; }
    .labels { display:flex; }
    .label { width:50%; text-align:center; font-size:0.78rem; text-transform:uppercase; letter-spacing:0.06em; color:#64748b; padding:8px; background:#0f172a; border-bottom:1px solid #334155; }
    .editor { display:flex; flex:1; overflow:hidden; }
    #editor { width:50%; padding:20px; background:#0f172a; color:#f8fafc; font-family:'Cascadia Code','Courier New',monospace; font-size:0.9rem; line-height:1.7; border:none; resize:none; border-right:2px solid #334155; }
    #editor:focus { outline:none; }
    #preview { width:50%; padding:20px; overflow-y:auto; }
    #preview h1,#preview h2,#preview h3 { margin:12px 0 8px; color:#f8fafc; }
    #preview h1 { font-size:1.8rem; border-bottom:1px solid #334155; padding-bottom:8px; }
    #preview h2 { font-size:1.4rem; }
    #preview p { margin-bottom:12px; line-height:1.7; color:#cbd5e1; }
    #preview code { background:#1e293b; padding:2px 6px; border-radius:4px; font-size:0.85em; color:#f59e0b; }
    #preview pre { background:#1e293b; padding:16px; border-radius:10px; margin:12px 0; overflow-x:auto; }
    #preview pre code { background:none; padding:0; color:#f8fafc; }
    #preview a { color:#3b82f6; }
    #preview blockquote { border-left:4px solid #3b82f6; margin:12px 0; padding:8px 16px; background:#162447; border-radius:0 8px 8px 0; }
    #preview strong { color:#f8fafc; }
    #preview ul { padding-left:20px; margin-bottom:12px; }
    #preview li { line-height:1.8; color:#cbd5e1; }
  </style>
</head>
<body>

<?php require_once __DIR__ . '/../promo_banner.php'; ?>

  <header>
    <h1>📝 Markdown Previewer</h1>
    <span style="color:#64748b;font-size:0.8rem">Live preview as you type</span>
  </header>
  <div class="labels"><div class="label">Markdown</div><div class="label">Preview</div></div>
  <div class="editor">
    <textarea id="editor" spellcheck="false" placeholder="Type Markdown here..."></textarea>
    <div id="preview"></div>
  </div>
  <script>
    // Paste parseMarkdown() from Step 1 here, then:
    const editor  = document.getElementById('editor');
    const preview = document.getElementById('preview');
    const SAMPLE  = `# Welcome to Markdown Previewer\n\nType in the **left panel** and see the rendered preview here.\n\n## Features\n\n- **Bold** text with \`**text**\`\n- *Italic* with \`*text*\`\n- \`Inline code\` with backticks\n- [Links](https://example.com)\n\n## Code blocks\n\n\`\`\`\nfunction hello() {\n  console.log("Hello, World!");\n}\n\`\`\`\n\n> Blockquotes look like this.`;

    editor.value = SAMPLE;
    editor.addEventListener('input', () => {
      preview.innerHTML = parseMarkdown(editor.value);
    });
    preview.innerHTML = parseMarkdown(SAMPLE);
  </script>
</body>
</html>
💡 Tip: For a production Markdown parser, use the marked.js library (markdownit.js is another option). These handle edge cases, tables, and strikethrough that a regex parser misses. Load marked from a CDN: <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js">

🎉 You Did It!

You wrote a custom Markdown parser and a live-preview editor. The regex-replacement approach handles 95% of common Markdown in 20 lines. The split-pane UI pattern is also used in code editors (Monaco), diagramming tools (Mermaid), and note-taking apps.

Found something wrong?

Spotted a bug, broken code, or something that doesn't look right? Tell us what's off and we'll fix it.