This article includes the steps to implement a simple Vanilla JavaScript Markdown editor on a webpage. You can use this information, for example, to publish your own Markdown editor online, add a Markdown editor to the back end of an application, or just have fun developing new things.

Why Building a JavaScript-Based Markdown Editor

Using a JavaScript-based solution is convenient over a server-based solution in the following scenarios:

  • For applications where providing a real-time preview of the formatted HTML can enhance the user experience.
  •  When an application needs to function offline or has limited server capabilities. A client-side Markdown to HTML parser enables rendering Markdown content without relying on a server for conversion.
  • When you need to reduce the processing load on the server, especially in scenarios with a large number of users or frequent content updates.

Implement a Markdown Parser

As a Markdown parser, this tutorial uses Marked. A powerful HTML to Markdown parser and compiler built with JavaScript and usable in standard HTML pages or with Node.js.

It’s worth noting that Marked can also be extended by developing custom extensions. Ready-to-use extensions are available here. For example, there are extensions to support emoji, highlight specific sections, automatically prefix URLs, etc.

Alternative Markdown to HTML JavaScript Parsers

Marked is just one of the many JavaScript-based Markdown parsers available. If, for any reason, you don’t like this parser, you might look into the following popular alternatives:

Basic Usage of the Marked Parser

To begin, load Marked in the HTML page using the provided CDN link or by referencing the production JavaScript file downloaded from GitHub.

<script src="./dist/marked.js"></script>

After that, we can use the parse() method to convert a string that includes Markdown text to HTML.

const generatedHTML = marked.parse('**Markdown** content.');
console.log(generatedHTML);

The code above will output this HTML:

<p><strong>Markdown</strong> content.</p>

Let’s Start Building the Markdown Editor

Create a Simple Markdown Editor With a Live Preview

In the following example, I’ll be creating a simple Markdown editor composed of a textarea used to edit the Markdown content and a div used to preview the resulting HTML.

<div id="editor">

<!-- The user will input the Markdown content in this textarea -->
<textarea id="markdown-content"></textarea>

<!-- The resulting HTML will be previewed here -->
<div id="html-preview"></div>

</div>

Then, I add an event listener to perform the Markdown to HTML conversion when the content of the textarea changes.

/**
* Event listener used to update the preview when the textarea changes.
*/
document.getElementById('markdown-content').addEventListener('input', function () {

    // Get references to the elements
    const markdownContent = document.getElementById('markdown-content');
    const htmlPreview = document.getElementById('html-preview');

    // Convert Markdown to HTML
    htmlPreview.innerHTML = marked.parse(markdownContent.value);

});

Sanitize the Output With DOMPurify

The Marked documentation states that this JavaScript library doesn’t sanitize the output. As a consequence, a library to perform the sanitization should be added and used. In the following step, I’ll use DomPurify, the library recommended in the Marked documentation.

So, let’s start by downloading DOMPurify on GitHub and then add it to the head section of the HTML page:

<script type="text/javascript" src="dist/purify.js"></script>

Now, I have to refactor the part used to update the Markdown preview. Note that to sanitize the HTML, I configure the USE_PROFILES attribute. This is useful to allow only HTML. (by default, DOMPurify also permits SVG and MathML, and we don’t want this)

document.getElementById('markdown-content').addEventListener('input', function () {

    // Get references to the elements
    const markdownContent = document.getElementById('markdown-content');
    const htmlPreview = document.getElementById('html-preview');

    // Convert Markdown to HTML. Note that the resulting HTML is now stored in a variable.
    htmlContent = marked.parse(markdownContent.value);

    // Sanitize the generated HTML and display it.
    htmlPreview.innerHTML = DOMPurify.sanitize(htmlContent,
            {USE_PROFILES: {html: true}});

});

Add Basic Styles

I’m going to style this Markdown editor with dark background colors and white text.

The CSS will be stored in a dedicated file named style.css, which is referenced in the main HTML document.

<link rel="stylesheet" href="./css/style.css">

The basic CSS rules of our stylesheet:

body {
    margin: 20px;
    background: #343d4e;
}

#editor {
    display: flex;
    position: relative;
    width: fit-content;
}

#markdown-content,
#html-preview {
    padding: 20px;
    width: 400px;
    height: 400px;
    overflow-y: auto;
}

#markdown-content {
    background: #202530;
    border: none;
    border-radius: 8px 0 0 8px;
    color: #fff;
    outline: none;
    resize: none;
}

#html-preview {
    background: #242a36;
    border-radius: 0 8px 8px 0;
    color: #fff;
}

The rules above do what follows:

  • The colors, borders, margins, and paddings of the elements are defined.
  • The two editor elements (the textarea and the Markdown preview) are positioned side by side.
  • A scrollbar is enabled on the textarea when necessary.

The final step is to add a button with an event listener to trigger the full-screen mode. When the button is clicked, a class is added to the #editor container.

document.getElementById('editor-mode').addEventListener('click', function () {

    // Toggle the presence of the class "distraction-free" on the element with the id "editor".
    document.getElementById('editor').classList.toggle('distraction-free');

});

The CSS for the fullscreen mode (the editor occupies the entire screen, and the two elements are 50% in width)

#editor-mode {
    position: absolute;
    top: 20px;
    right: 20px;
    background: #2e3440;
    color: #fff;
    border: none;
    border-radius: 8px;
    padding: 8px 16px;
    font-weight: 700;
    cursor: pointer;
}

#editor.distraction-free {
    width: 100%;
    height: 100%;
}

#editor.distraction-free #markdown-content,
#editor.distraction-free #html-preview {
    width: 100%;
    height: calc(100vh - 42px);
}
Markdown editor in a webpage with a button to enable the distraction-free mode.
Our simple JavaScript-based Markdown editor after applying CSS styles and the distraction-free mode.

Possible Upgrades to the Markdown Editor

In this tutorial, we covered creating a basic Markdown editor. However, real-world Markdown editors tend to include additional advanced features.

You might consider augmenting this JavaScript-based Markdown editor with these additional functionalities:

  • Syntax highlighter – A syntax highlighter applied to the text in the textarea.
  • Buttons and hotkeys – Buttons and hotkeys to quickly include Markdown syntax in the textarea.
  • Support for multiple Markdown flavors – Ability to change the Markdown flavor in use. This can be done by changing the Marked parser options or by using multiple Markdown parsers.
  • Save/Export – Depending on the context; you might consider adding the ability for your user to save or export the written Markdown content, for example, in a database table.
  • Spell Checking – An integrated spell check might be useful to quickly correct errors.
  • Emoji support – You might consider adding support for emoji. Note that for this, there is an Emoji extension for Marked
  • Accessibility features – Consider adding accessibility features to ensure the editor is usable by individuals with disabilities.

The Complete Code of Our Vanilla JavaScript Markdown Editor

Here is the complete HTML file used to add the Markdown editor HTML to the page and to handle the events with JavaScript:

<!doctype html>
<html>
<head>
	<meta charset="utf-8"/>
	<title>Simple Markdown editor in the browser</title>
</head>
<body>
<div id="editor">
	<textarea id="markdown-content"></textarea>
	<div id="html-preview"></div>
	<button id="editor-mode">Change Mode</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="./dist/purify.js"></script>
<link rel="stylesheet" href="./css/style.css">
<script>

    // Event listener used to update the preview when the "markdown-content" textarea changes.
    document.getElementById('markdown-content').addEventListener('input', function () {

        // Get references to the elements.
        const markdownContent = document.getElementById('markdown-content');
        const htmlPreview = document.getElementById('html-preview');

        // Convert Markdown to HTML.
        const htmlContent = marked.parse(markdownContent.value);

        // Sanitize the generated HTML and display it.
        htmlPreview.innerHTML = DOMPurify.sanitize(htmlContent,
            {USE_PROFILES: {html: true}});

    });

    document.getElementById('editor-mode').addEventListener('click', function () {

        // Toggle the presence of the class "distraction-free" on the element with the id "editor".
        document.getElementById('editor').classList.toggle('distraction-free');

    });

</script>
</body>
</html>

This is the complete stylesheet used to apply the basic Markdown editor style:

body {
    margin: 20px;
    background: #343d4e;
}

#editor {
    display: flex;
    position: relative;
    width: fit-content;
}

#markdown-content,
#html-preview {
    padding: 20px;
    width: 400px;
    height: 400px;
    overflow-y: auto;
}

#markdown-content {
    background: #202530;
    border: none;
    border-radius: 8px 0 0 8px;
    color: #fff;
    outline: none;
    resize: none;
}

#html-preview {
    background: #242a36;
    border-radius: 0 8px 8px 0;
    color: #fff;
}

#editor-mode {
    position: absolute;
    top: 20px;
    right: 20px;
    background: #2e3440;
    color: #fff;
    border: none;
    border-radius: 8px;
    padding: 8px 16px;
    font-weight: 700;
    cursor: pointer;
}

#editor.distraction-free {
    width: 100%;
    height: 100%;
}

#editor.distraction-free #markdown-content,
#editor.distraction-free #html-preview {
    width: 100%;
    height: calc(100vh - 42px);
}