Skip to main content

The Native-First, High-Performance Markdown Plugin for JavaScript

MarkdownEditor transforms a standard HTML <textarea> into a professional, feature-rich Markdown suite. Designed for developers who value simplicity, speed, and SEO, it integrates seamlessly into your existing forms without requiring complex API logic.

The Philosophy: "Native-First"

Most editors break the standard web workflow. MarkdownEditor embraces it. Because it sits directly on top of a <textarea>, you don't need to learn a new way to handle data.

  • No Data Binding Needed: Works with <form method="POST"> out of the box
  • Standard Access: Use document.getElementById('editor').value just like a normal input.
  • Backend Agnostic: Works with any backend (Python, Node.js, PHP, etc.) just like a normal form field

Key Features

πŸ–ΌοΈ Advanced Image Upload (SEO Optimized)

Don't bloat your database with heavy Base64 strings. Configure our API to upload images directly to your server or S3 bucket. The editor receives the URL, keeping your Markdown files light and your site's SEO ranking high.

πŸ”€ Hybrid & Plain Modes

Give your users the best of both worlds. Switch between a Visual (WYSIWYG) Hybrid mode for easy formatting and a Plain Markdown mode for distraction-free, raw syntax editing.

🌍 Global-Ready with RTL Support

Full native support for Right-to-Left (RTL) languages. Perfect for projects requiring Arabic, Urdu, or Farsi support with automatic text-direction alignment.

⚑ Performance at Scale

  • Lightweight: A tiny ~116KB footprint that won't slow down your page load
  • Large Document Support: Optimized to handle thousands of lines of text without input lag or browser freezing
  • Smart Rendering: Debounced preview updates, cached style calculations, and conflict-free keyboard handling between list continuation and indentation β€” so Tab and Enter always do exactly one thing

β™Ώ Accessible by Default

Full ARIA support is built in and requires no extra configuration. The toolbar is a proper role="toolbar" landmark, the preview pane is a labelled role="region", all SVG icons are hidden from screen readers, the preview toggle exposes its on/off state via aria-pressed, and disabled toolbar buttons use aria-disabled so assistive technology is never misled. Modals return focus to the triggering button when closed.

πŸ›‘οΈ Zero CSS Conflicts

All editor styles are fully scoped to the .markdown-editor-wrapper element. Tailwind's global preflight (element resets for h1–h6, a, button, etc.) is excluded, so the editor can live alongside Bootstrap, Tailwind, or any other CSS framework on the same page without breaking a single style.

Markdown Editor Demo

Quick Implementation

1. Installation

You can install it using NPM, import the JavaScript file directly, or use a CDN for rapid deployment.

Install via NPM

bash
npm install markdown-text-editor
OR

Using a CDN

Alternatively, include the following CDN links in your HTML

html
<script src="https://cdn.jsdelivr.net/npm/markdown-text-editor"></script>

2. The HTML Structure

Simply place a <textarea> inside your standard form. No special wrappers required.

html
1
2
3
4
<form action="/api/save" method="POST">
  <textarea id="markdown-editor" name="content"># Hello World</textarea>
  <button type="submit">Save Content</button>
</form>

3. Import JS and CSS and initialise the plugin on your textarea element

javascript
1
2
3
4
5
6
import MarkdownEditor from "markdown-text-editor";

const editor = new MarkdownEditor('#markdown-editor', {
  placeholder: 'Write your markdown...',
  toolbar: ['heading', 'bold', 'italic', 'strikethrough', 'ul', 'ol', 'checklist', 'blockquote', 'link', 'preview'],
});

4. Configuration & Customization

You can fully customize the editor's behavior and interface by passing an options object. If you omit an option, the default value is used.

Property Type Default Purpose
mode string 'plain' Sets the initial view. Use 'hybrid' for a WYSIWYG experience or 'plain' for raw syntax.
placeholder string 'Write...' Text shown when the editor is empty.
toolbar array [...] Defines which tools appear and in what order.
footer false | object all visible Controls the status bar shown below the editor. Set to false to hide it entirely, or pass an object to toggle individual stats.
theme string inherited Explicitly sets the editor theme ('light', 'dark', 'snowberry', 'darkberry'). If omitted, the editor inherits data-theme from the nearest ancestor element or the <textarea> itself.

πŸ›  Toolbar Customization

The toolbar is modular. You can create a minimal experience or a full-featured power suite by modifying the array.

Available Tools
Category Tool Keys
Typography heading, bold, italic, strikethrough, blockquote
Lists ul (bullet), ol (numbered), checklist
Code code (inline), codeblock (fenced block)
Inserts hr (horizontal rule), table (table template)
Media link, image
Editing undo, redo, indent, outdent
View preview
πŸ’‘ Implementation Tips:
  • Reordering: The buttons appear in the exact order you list them in the array
  • Removing: Simply omit any key (like image) from the array to disable that feature entirely for the user
  • Native Fallback: If you don't provide a placeholder in JS, the plugin will automatically use the placeholder attribute from your HTML <textarea>

πŸ“Š Footer (Status Bar)

The footer sits below the editor and shows the cursor's line, column, and the document's character count β€” all updated in real time. It is visible by default and each stat can be toggled independently.

Key Type Default Description
line boolean true Show the current line number.
col boolean true Show the current column number.
chars boolean true Show the total character count.
Usage Examples
javascript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Default β€” all stats visible
new MarkdownEditor('#editor');

// Disable the footer entirely
new MarkdownEditor('#editor', { footer: false });

// Hide only character count
new MarkdownEditor('#editor', { footer: { chars: false } });

// Hide line and column, keep character count
new MarkdownEditor('#editor', { footer: { line: false, col: false } });

// Show only line number
new MarkdownEditor('#editor', { footer: { col: false, chars: false } });

5. Getting, Setting, and Submitting Content

One of the core strengths of MarkdownEditor is that it keeps the underlying <textarea> perfectly synchronized. Whether you are using a modern JavaScript framework or a traditional backend like Django, PHP, or Laravel, the workflow remains simple and native.

1. The Native Way (Recommended)

Because the editor enhances a standard textarea, you can use familiar DOM methods. This is the fastest way to interact with your data without learning a new API.

javascript
1
2
3
4
5
// Retrieve content via ID
const markdown = document.getElementById('markdown-editor').value;

// Set content via ID (The editor UI updates automatically)
document.getElementById('markdown-editor').value = "# New Heading Content";

2. Using a Variable Reference

If you have a reference to the textarea element, you can use it directly β€” no library-specific API needed.

javascript
1
2
3
4
5
6
7
const textarea = document.getElementById('markdown-editor');

// Retrieve content
const markdown = textarea.value;

// Set content (the editor UI reflects this immediately)
textarea.value = "## Updated via JS";

3. Effortless Automatic Form Submission

Because MarkdownEditor is built directly on the native <textarea>, it is compatible with every backend framework (Django, Laravel, PHP, Ruby on Rails, etc.) right out of the box.

This is where the "Native-First" philosophy shines. You don't need to manually sync data before submitting a form. The browser treats the editor exactly like a standard input field.

html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<form method="POST" action="/api/submit">
  <textarea id="markdown-editor" name="content" class="h-48" rows="5">
    # Initial Content
  </textarea>
  
  <button type="submit">Submit to Server</button>
</form>

<script>
  // Just initialize it. That's it.
  new MarkdownEditor('#markdown-editor');
</script>

Note: MarkdownEditor plugin initialization mandatory

Just use a standard HTML <form>. The name attribute on the textarea is what your server will use to identify the content.

πŸš€ Why it's a Game-Changer for Backends

Since the editor preserves the native <textarea> behavior, your server handles the data as a standard string. There is zero extra logic requiredβ€”no preventDefault() and no manual FormData construction.

πŸ’‘ Why this is a "Killer Feature":

Most editors (like Quill, Editor.js, simpleMDE, easyMDE) save data in complex JSON structures. If a developer uses those, they have to rewrite their database schema and their rendering logic.

With MarkdownEditor, a developer can take an old website, replace a plain <textarea> with your editor, and the backend doesn't even know it changed. It just receives the same raw text it always did, but the user gets a 10x better experience.

Framework / Language How to access the Markdown content
PHP $_POST['content']
Django request.POST.get('content')
Node.js (Express) req.body.content
Laravel $request->input('content')
Ruby on Rails params[:content]

Configuration Options

πŸ–ΌοΈ Advanced image upload

Handling image uploads nativelyβ€”rather than relying on slow, memory-heavy Base64 stringsβ€”is a significant win for both performance and SEO.

Configuration Options

The image tool supports a fileInput configuration to handle direct server uploads.

  • accept: Define an array of allowed image formats (e.g., 'webp', 'avif')
  • uploadUrl: Specify the backend endpoint where the File object will be sent via POST
  • params: Optional object to send additional data (like CSRF tokens, user IDs, or folder names) alongside the image file

Usage example (Full Config)

javascript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
const options = {
  placeholder: 'Start writing...',
  toolbar: [
    'link',
    {
      image: {
        fileInput: {
            accept: ['webp', 'avif'], // restrict the image upload format
            uploadUrl: '/api/upload', // Your upload endpoint
            params: {
                _token: 'your_csrf_token_here', // Essential for Laravel/Django
                folder: 'blog_posts'
            }
        },
        // Supports boolean: true/false OR object: { required: true }
        altInput: { required: true }
      }
    },
    'preview'
  ],
}
const editor = new MarkdownEditor('#markdown-editor', options);

πŸ“‘ Server Integration Details

1. The Request

The editor sends a POST request as multipart/form-data. By default, it includes:

  • image_file: The actual file object
  • image_alt: The alt text entered by the user
  • ...plus any custom data defined in the params object
2. The Required Response

To confirm a successful upload and insert the image into the editor, your server must return the following JSON structure:

json
1
2
3
4
{
  "success": true,
  "image_path": "https://cdn.yourdomain.com/uploads/image.webp"
}

Note: Ensure you use the key image_path for the URL of the uploaded image.

Image Alt Text Validation (altInput)

To ensure your content remains accessible and SEO-friendly, MarkdownEditor enforces alt text validation by default. You can configure this behavior using either a boolean shorthand or a detailed object.

  • Default Behavior: If altInput is not defined, it defaults to { required: true }
  • Enforce Accessibility: Users will be prevented from inserting an image until an alt description is provided
Configuration Examples:
1. Default (No configuration needed)
javascript
1
2
3
4
// Alt text is REQUIRED by default
image: { 
  fileInput: { uploadUrl: '/api/upload' } 
}
2. Shorthand (Disable Validation)

If you want to allow images without descriptions, simply set the boolean to false.

javascript
1
2
3
image: { 
  altInput: false // Users can now skip the alt text field
}
3. Object-based (Explicit)
javascript
1
2
3
4
5
image: {
    altInput: {
        required: false // Disables alt text validation β€” users can skip the alt field
    }
}

Standard Image Usage (No fileInput):

If fileInput is not configured, the editor defaults to a simple URL-based modal. This is ideal if your users are mostly linking to external image hosts.

javascript
1
2
3
4
5
6
7
8
const options = {
  toolbar: [
    'link',
    'image',
    'preview'
  ],
}
const editor = new MarkdownEditor('#markdown-editor', options);
πŸ’‘ Why use params?

In frameworks like Laravel or Django, you cannot upload files without a CSRF token. By adding _token to the params object, your request will pass through the backend's security middleware seamlessly, maintaining the "Zero Logic" philosophy for your server-side controllers.

πŸ”€ Editing Modes

MarkdownEditor offers two distinct ways to write and format your content. You can toggle between a traditional syntax-focused view or a modern, visual-first experience.

Configuration

You can define the starting mode using the mode property during initialization.

  • plain (Default): A clean, high-performance Markdown environment where syntax (like **bold** or # heading) is visible. Ideal for developers and Markdown purists
  • hybrid: A WYSIWYG-inspired experience that renders formatting (Bold, Italics, Headings) in real-time as you type, while still maintaining the underlying Markdown structure.
Implementation:
javascript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Default initialization (Plain Mode)
new MarkdownEditor('#markdown-editor');

// Explicit Plain Mode
new MarkdownEditor('#markdown-editor', {
    mode: 'plain'
});

// Hybrid (Visual) Mode
new MarkdownEditor('#markdown-editor', {
    mode: 'hybrid'
});
Hybrid and Plain Mode Preview:
Hybrid Mode

Visual formatting is rendered in real-time while you type.

Plain Mode (Default)

Focuses on raw Markdown syntax for a lightweight experience.

Config Property / Tool Description
Options object placeholder Sets the placeholder text for the textarea (optional, as you can also use the standard HTML textarea attribute)
mode: 'hybrid' Enables a WYSIWYG-inspired experience that renders formatting (bold, italic, headings) in real-time as you type
toolbar: Determines which tools
appear in the toolbar and their order.
heading Opens a dropdown to select heading level H1–H6
bold Enables bold text formatting.
italic Enables italic text formatting.
strikethrough Allows for text strikethrough.
ol (Ordered List): Converts text into a numbered list format.
ul (Unordered List): Converts text into a bullet point list.
checklist Adds checkboxes to your text, making it great for tasks, to-do lists, or tracking completion status.
blockquote Highlight quoted or emphasized text.
code Wraps selected text in single backticks for inline code. Clicking again removes the backticks.
codeblock Wraps selected text in a triple-backtick fenced code block. Clicking again removes the fences.
hr Inserts a --- horizontal rule at the cursor position on its own line.
table Inserts a starter 2x3 markdown table template at the cursor position.
image Allows you to insert images via markdown syntax.
link Lets you add hyperlinks to your text.
undo To reverse the last changes.
redo To reapply the last undone changes.
indent To increase the indentation level.
outdent To decrease the indentation level.
preview Toggles the real-time markdown preview. Checkboxes in the preview pane are clickable and update the markdown source instantly.
Advanced image upload feature:
Enables configuring image uploads to
your own server and setting the image
path via an API. This improves performance and SEO.
fileInput accept: Array of allowed image file types (e.g. 'webp', 'avif').
uploadUrl: Backend endpoint where the file is sent via POST.
params: Optional object for extra data (CSRF tokens, user IDs, folder names)
altInput required: false: Disables alt-text input validation (default is true)

πŸŒ™ Theming

MarkdownEditor automatically inherits its theme from the surrounding page β€” no configuration required. The editor reads data-theme from the nearest ancestor at initialisation, so it stays in sync with your site's theme out of the box.

How theme is resolved (priority order)

  1. theme option β€” explicit override passed in the options object
  2. data-theme on the <textarea> β€” set directly on the element
  3. data-theme on any ancestor β€” e.g. <html>, <body>, or a wrapper <div>

Available themes

'light' (default), 'dark', 'snowberry', 'darkberry'

Option 1 β€” inherit from <html> or any ancestor (zero config)
html
1
2
3
4
5
6
<html data-theme="dark">
  ...
  <textarea id="markdown-editor"></textarea>
  <script>
    new MarkdownEditor('#markdown-editor'); // picks up dark automatically
  </script>
Option 2 β€” set data-theme directly on the <textarea>
html
1
2
3
4
<textarea id="markdown-editor" data-theme="dark"></textarea>
<script>
  new MarkdownEditor('#markdown-editor');
</script>
Option 3 β€” explicit theme option (overrides everything)
javascript
1
2
3
new MarkdownEditor('#markdown-editor', {
    theme: 'dark'
});

🎨 Custom Theme via CSS Variables

You can fully customize the editor's look by overriding its CSS variables on the .markdown-editor-wrapper element or any [data-theme] selector. All colors use the OKLCH color space for perceptually uniform results.

Variable Purpose Light default Dark default
--color-base Editor background oklch(100% 0 0) oklch(10.9% 0 0)
--color-on-base Primary text color oklch(22% 0 0) oklch(98% 0 0)
--color-primary Primary accent (toolbar active, links) oklch(51.1% .262 277) oklch(66.4% .184 286)
--color-on-primary Text on primary-colored surfaces oklch(96.2% .018 272) oklch(10% .01 270)
--color-secondary Secondary accent oklch(59.1% .293 323) oklch(65% .18 220)
--color-accent Highlight accent (inline code, italic) oklch(54.1% .281 293) oklch(75% .18 50)
--color-neutral Neutral surfaces (borders, dividers) oklch(15% 0 0) oklch(85% 0 0)
--color-error Error state color oklch(57.7% .245 27) oklch(60% .22 30)
--border-radius Corner rounding of the editor frame 0.25rem
Custom theme example

Override any variable on .markdown-editor-wrapper after the editor initialises, or define a custom [data-theme] block in your stylesheet:

css
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/* Override individual variables */
.markdown-editor-wrapper {
    --color-primary: oklch(60% 0.2 30);   /* orange accent */
    --border-radius: 0.5rem;
}

/* Or define a full custom theme */
[data-theme="brand"] .markdown-editor-wrapper,
.markdown-editor-wrapper[data-theme="brand"] {
    --color-base:       oklch(15% 0.01 250);
    --color-on-base:    oklch(95% 0 0);
    --color-primary:    oklch(65% 0.22 145);   /* green */
    --color-on-primary: oklch(10% 0 0);
    --color-accent:     oklch(75% 0.18 60);
    --color-neutral:    oklch(80% 0 0);
    --border-radius:    0.75rem;
}
javascript
new MarkdownEditor('#markdown-editor', { theme: 'brand' });
  • Real-time Preview: See your markdown rendered instantly as you type.
  • Syntax Highlighting: Enhanced readability with clear code and markdown formatting.
  • Easy Integration: Seamlessly integrate into any web project with minimal setup.
  • Customizable Toolbar: Dynamically configure and reorder toolbar options like bold, italic, and more.

Features

πŸ”Œ Native Form Integration

Works exactly like a standard <textarea>. No complex APIsβ€”just use the value or name attribute. It "just works" with standard HTML form submissions in PHP, Django, or Node.js.

πŸ–ΌοΈ Advanced Image Upload

Configure native server uploads via API. Avoid heavy Base64 strings to ensure faster page loads and superior SEO by hosting images on your own CDN.

πŸ”€ Hybrid & Plain Modes

Switch between a Hybrid (WYSIWYG) experience for visual editing or Plain Markdown mode for a traditional coding feel.

πŸš€ High Performance

A tiny ~116KB bundle optimized for "Heavy Content." Handles massive documents and large files without any input lag or performance drop.

🌍 Built-in RTL Support

Native support for Right-to-Left languages like Arabic, Urdu, and Farsi. Perfect for building globally accessible applications.

πŸŒ™ Adaptive Theming

Includes automatic Dark Mode support. It syncs with your system settings or the Frutjam UI library for a seamless visual experience.

πŸ“ Smart Editing

GitHub-style automatic list continuation for ordered lists, unordered lists, and checklists β€” press Enter and the editor continues the pattern. Checkboxes in the preview pane are clickable and sync back to the markdown source instantly.

πŸ“± Fully Responsive

A fluid, mobile-first UI that adapts perfectly to desktops, tablets, and smartphones for editing on the go.

πŸ“¦ Universal Support

Compatible with ESM, UMD, CommonJS, and IIFE. Works out of the box via CDN (<script src>), npm, or any bundler (Vite, webpack, Rollup) β€” no extra configuration needed.

β™Ώ Accessible by Default

Full ARIA support built in β€” toolbar landmark, labelled preview region, screen-reader-friendly buttons, aria-pressed on the preview toggle, aria-disabled on inactive tools, and correct focus restoration when modals close.

πŸ›‘οΈ Zero CSS Conflicts

Editor styles are fully scoped to .markdown-editor-wrapper. Tailwind's global preflight is excluded so the editor lives safely alongside Bootstrap, Tailwind, or any other framework without breaking their styles.

Full Configuration Example

Use this comprehensive example to initialize MarkdownEditor with all primary features, including custom toolbar ordering and advanced image upload handling.

javascript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const editor = new MarkdownEditor('#markdown-editor', {
    mode: 'hybrid',
    placeholder: 'Start writing...',
    footer: {
        line: true,
        col: true,
        chars: true,
    },
    toolbar: [
        'heading', 'bold', 'italic', 'strikethrough', 'blockquote',
        'ul', 'ol', 'checklist',
        'code', 'codeblock', 'hr', 'table',
        {
            image: {
                fileInput: {
                    accept: ['webp', 'avif', 'png'],
                    uploadUrl: '/api/upload'
                }
            }
        },
        'link', 'undo', 'redo', 'indent', 'outdent', 'preview'
    ],
});