Profile
Back to NewsBack
GitHub Trending 22 min
Reader Mode
panphora/overtype: The markdown editor that's just a textarea https://overtype.dev

panphora/overtype: The markdown editor that's just a textarea https://overtype.dev

12 hours ago

OverType

A lightweight markdown editor library with perfect WYSIWYG alignment using an invisible textarea overlay technique. Includes optional toolbar. ~135KB minified with all features.

Live Examples

🎮 Try it out: Interactive demos on overtype.dev

Features

  • 👻 Invisible textarea overlay - Transparent input layer overlaid on styled preview for seamless editing
  • 🎨 Global theming - Solar (light) and Cave (dark) themes that apply to all instances
  • ⌨️ Keyboard shortcuts - Common markdown shortcuts (Cmd/Ctrl+B for bold, etc.)
  • 📱 Mobile optimized - Responsive design with mobile-specific styles
  • 🔄 DOM persistence aware - Recovers from existing DOM (perfect for HyperClay and similar platforms)
  • 🚀 Lightweight - ~135KB minified
  • 🎯 Optional toolbar - Clean, minimal toolbar with all essential formatting
  • ✨ Smart shortcuts - Keyboard shortcuts with selection preservation
  • 📝 Smart list continuation - GitHub-style automatic list continuation on Enter
  • 🔧 Framework agnostic - Works with React, Vue, vanilla JS, and more

How it works

!OverType Architecture Diagram

We overlap an invisible textarea on top of styled output, giving the illusion of editing styled text using a plain textarea.

Comparisons

| Feature | OverType | HyperMD | Milkdown | TUI Editor | EasyMDE | |---------|----------|---------|----------|------------|---------| | Size | ~135KB | 364.02 KB | 344.51 KB | 560.99 KB | 323.69 KB | | Dependencies | Bundled | CodeMirror | ProseMirror + plugins | Multiple libs | CodeMirror | | Setup | Single file | Complex config | Build step required | Complex config | Moderate | | Approach | Invisible textarea | ContentEditable | ContentEditable | ContentEditable | CodeMirror | | Mobile | Perfect native | Issues common | Issues common | Issues common | Limited | | Markdown syntax | Visible | Hidden | Hidden | Toggle | Visible | | Advanced features | Basic | Full | Full | Full | Moderate | | Best for | Simple, fast, mobile | Full WYSIWYG | Modern frameworks | Enterprise apps | Classic editing |

Choose OverType when you need:

  • Tiny bundle size (10x smaller than alternatives)
  • Zero dependencies - single file that works immediately
  • Perfect native browser features (undo/redo, mobile keyboards, optional spellcheck)
  • Dead-simple integration without build tools
  • Easy to understand, modify, and extend
  • Excellent mobile support with visible markdown syntax
Choose other editors when you need:
  • Full WYSIWYG with hidden markdown syntax
  • Advanced features like tables, diagrams, or collaborative editing
  • Rich plugin ecosystems
  • Enterprise features and extensive customization
  • Framework-specific integration (React, Vue, etc.)
  • Complex multi-layered architecture for deep customization

Installation

NPM (bundlers: Vite, webpack, Rollup, etc.)

npm install overtype
import OverType from 'overtype';

const [editor] = new OverType('#editor', { value: '# Hello' });

OverType is ESM-first and also ships a CommonJS build, so both styles work:

// ESM — default or named import
import OverType from 'overtype';
import { OverType } from 'overtype';

// CommonJS const { OverType } = require('overtype');

CDN: native ES module (no build step)

<script type="module">
  import OverType from 'https://cdn.jsdelivr.net/npm/overtype@latest/dist/overtype.esm.js';
  const [editor] = new OverType('#editor', { value: '# Hello' });
</script>

CDN: global script tag (IIFE)

<script src="https://cdn.jsdelivr.net/npm/overtype@latest/dist/overtype.min.js"></script>
<script>
  const [editor] = new OverType('#editor', { value: '# Hello' });
</script>

Quick Start

// Create a single editor
const [editor] = new OverType('#editor', {
  value: '# Hello World',
  theme: 'solar'
});

// Get/set content editor.getValue(); editor.setValue('# New Content');

// Change theme for this instance editor.setTheme('cave');

Usage

Basic Editor

<div id="editor" style="height: 400px;"></div>

<script> const [editor] = new OverType('#editor', { placeholder: 'Start typing markdown...', value: '# Welcome\n\nStart writing markdown here!', onChange: (value, instance) => { console.log('Content changed:', value); } }); </script>

Toolbar

// Enable default toolbar with all formatting buttons
const [editor] = new OverType('#editor', {
  toolbar: true,
  value: '# Document\n\nSelect text and use the toolbar!'
});

// Default toolbar: Bold, Italic, Code | Link | H1, H2, H3 | Lists, Tasks | Quote | View Mode

Custom Toolbar (v2.0):

import OverType, { toolbarButtons } from 'overtype';

const [editor] = new OverType('#editor', { toolbar: true, toolbarButtons: [ toolbarButtons.bold, toolbarButtons.italic, toolbarButtons.separator, { name: 'save', icon: '<svg>...</svg>', title: 'Save', action: ({ editor, getValue }) => { localStorage.setItem('draft', getValue()); } } ] });

// Available: bold, italic, code, link, h1, h2, h3, bulletList, // orderedList, taskList, quote, separator, viewMode

Custom buttons that behave like toggle buttons can provide isActive. When it returns true or false, OverType updates the button’s active class and aria-pressed state:

{
  name: 'customToggle',
  icon: '<svg>...</svg>',
  title: 'Custom Toggle',
  isActive: ({ editor, activeFormats }) => activeFormats.includes('bold'),
  action: ({ editor }) => {
    // Toggle your custom formatting
  }
}

Driving formatting from your own UI:

OverType re-exports the bundled markdown-actions library so you can build a fully custom toolbar (or any UI) without installing or bundling markdown-actions separately:

import OverType, { markdownActions } from 'overtype';

const [editor] = new OverType('#editor');

// Apply formatting to the editor's textarea directly document.querySelector('#bold-btn').addEventListener('click', () => { markdownActions.toggleBold(editor.textarea); editor.textarea.focus(); });

Available actions include toggleBold, toggleItalic, toggleCode, insertLink, toggleBulletList, toggleNumberedList, toggleQuote, toggleTaskList, insertHeader, toggleH1/H2/H3, getActiveFormats, hasFormat, expandSelection, and applyCustomFormat. The same namespace is available as window.markdownActions and OverType.markdownActions in script-tag builds.

See examples/custom-toolbar.html for complete examples.

View Modes

Three modes available via toolbar dropdown or programmatically:

editor.showNormalEditMode();   // Default WYSIWYG editing
editor.showPlainTextarea();    // Raw markdown, no preview
editor.showPreviewMode();      // Read-only preview with clickable links

Task Lists

GitHub-style checkboxes render in preview mode:

const [editor] = new OverType('#editor', {
  value: '- [ ] Todo\n- [x] Done',
  toolbar: true  // Use view mode dropdown to see checkboxes
});

// Edit mode: Shows - [ ] and - [x] syntax (preserves alignment) // Preview mode: Renders actual checkbox inputs

Syntax Highlighting

import { codeToHtml } from 'shiki';

// Global highlighter (all instances) OverType.setCodeHighlighter((code, lang) => codeToHtml(code, { lang, theme: 'nord' }) );

// Per-instance override const [editor] = new OverType('#editor', { codeHighlighter: (code, lang) => myHighlighter(code, lang) });

See docs/SYNTAX_HIGHLIGHTING.md for complete guide.

Keyboard Shortcuts

The toolbar and keyboard shortcuts work together seamlessly:

  • Cmd/Ctrl + B - Bold
  • Cmd/Ctrl + I - Italic
  • Cmd/Ctrl + K - Insert link
  • Cmd/Ctrl + Shift + 7 - Numbered list
  • Cmd/Ctrl + Shift + 8 - Bullet list
  • Cmd/Ctrl + ] - Indent current line or selection
  • Cmd/Ctrl + [ - Outdent current line or selection
  • Tab / Shift+Tab - Indent or outdent selected lines
Collapsed Tab and Shift+Tab follow normal browser focus navigation so keyboard users can leave the editor. Editing shortcuts preserve text selection, allowing you to apply multiple formats quickly.

Disabling Features

Turn off line-level Markdown features for one editor, for example a comment box without headings. A disabled feature renders as plain text, so # Hello stays the literal text "# Hello" in the editor and in preview mode. Matching toolbar buttons are hidden and their keyboard shortcuts do nothing.

new OverType('#comment', {
  toolbar: true,
  disable: ['headings', 'blockquote']
});

Names: h1, h2, h3, headings (all three), lists (also turns off tasks and smart list continuation), tasks, blockquote, code (fenced code blocks), hr. Inline formatting (bold, italic, links, inline code) can't be disabled. Change it later with editor.reinit({ disable: [...] }).

Multiple Editors

// Initialize multiple editors at once
const editors = OverType.init('.markdown-editor', {
  theme: 'cave',
  fontSize: '16px'
});

// Each editor is independent editors.forEach((editor, index) => { editor.setValue(# Editor ${index + 1}); });

Form Integration

// Use with form validation
const [editor] = new OverType('#message', {
  placeholder: 'Your message...',
  textareaProps: {
    required: true,
    maxLength: 500,
    name: 'message'
  }
});

// The textarea will work with native form validation document.querySelector('form').addEventListener('submit', (e) => { const content = editor.getValue(); // Form will automatically validate required field });

File Uploads

OverType handles paste and drop of files when fileUpload is configured. You upload to your own backend in onInsertFile and return the markdown to insert. When that markdown link is later removed from the editor, onRemoveFile fires so you can clean up the backend file.

const [editor] = new OverType('#editor', {
  fileUpload: {
    enabled: true,
    maxSize: 10  1024  1024,                  // 10MB
    mimeTypes: ['image/png', 'image/jpeg'],     // optional whitelist; empty = accept all
    batch: false,                               // true = one onInsertFile call per drop

// Upload to your backend, return the markdown link to insert onInsertFile: async (file) => { const { url } = await uploadToBackend(file); const isImage = file.type.startsWith('image/'); return isImage ? !${file.name} : ${file.name}; },

// Optional: fires when an inserted link is removed from the editor. // Useful for deleting orphaned files from storage. onRemoveFile: ({ url, filename, file }) => { fetch(/api/files/${encodeURIComponent(url)}, { method: 'DELETE' }); } } });

onRemoveFile only fires for URLs that OverType originally inserted via onInsertFile. URL edits, manual paste of an existing link, or programmatic edits to non-tracked URLs do not trigger it.

See examples/file-upload.html for a complete working demo.

Custom Theme

const [editor] = new OverType('#editor', {
  theme: {
    name: 'my-theme',
    colors: {
      bgPrimary: '#faf0ca',
      bgSecondary: '#ffffff',
      text: '#0d3b66',
      h1: '#f95738',
      h2: '#ee964b',
      h3: '#3d8a51',
      strong: '#ee964b',
      em: '#f95738',
      link: '#0d3b66',
      code: '#0d3b66',
      codeBg: 'rgba(244, 211, 94, 0.2)',
      blockquote: '#5a7a9b',
      hr: '#5a7a9b',
      syntaxMarker: 'rgba(13, 59, 102, 0.52)',
      cursor: '#f95738',
      selection: 'rgba(244, 211, 94, 0.4)'
    }
  }
});

Preview & HTML Export

Generate HTML previews or export the rendered content:

const [editor] = new OverType('#editor', {
  value: '# Title\n\nBold text with links'
});

// Get the raw markdown const markdown = editor.getValue(); // Returns: "# Title\n\nBold text with links"

// Get rendered HTML with syntax markers (for debugging/inspection) const html = editor.getRenderedHTML(); // Returns HTML with <span class="syntax-marker"> elements visible

// Get clean HTML for export (no OverType-specific markup) const cleanHTML = editor.getRenderedHTML({ cleanHTML: true }); // Returns clean HTML suitable for saving/exporting

// Convenience method for clean HTML const exportHTML = editor.getCleanHTML(); // Same as getRenderedHTML({ cleanHTML: true })

// Get the current preview element's HTML (actual DOM content) const previewHTML = editor.getPreviewHTML(); // Returns exactly what's shown in the editor's preview layer

// Example: Export clean HTML to server const htmlToSave = editor.getCleanHTML(); // No syntax markers // Example: Clone exact preview appearance document.getElementById('clone').innerHTML = editor.getPreviewHTML();

Stats Bar

Enable a built-in stats bar that shows character, word, and line counts:

// Enable stats bar on initialization
const [editor] = new OverType('#editor', {
  showStats: true
});

// Show or hide stats bar dynamically editor.showStats(true); // Show editor.showStats(false); // Hide

// Custom stats format const [editor] = new OverType('#editor', { showStats: true, statsFormatter: (stats) => { // stats object contains: { chars, words, lines, line, column } return <span>${stats.chars} characters</span> <span>${stats.words} words</span> <span>${stats.lines} lines</span> <span>Line ${stats.line}, Col ${stats.column}</span>; } });

The stats bar automatically adapts to your theme colors using CSS variables.

React Component

function MarkdownEditor({ value, onChange }) {
  const ref = useRef();
  const editorRef = useRef();
  
  useEffect(() => {
    const [instance] = OverType.init(ref.current, {
      value,
      onChange
    });
    editorRef.current = instance;
    
    return () => editorRef.current?.destroy();
  }, []);
  
  useEffect(() => {
    if (editorRef.current && value !== editorRef.current.getValue()) {
      editorRef.current.setValue(value);
    }
  }, [value]);
  
  return <div ref={ref} style={{ height: '400px' }} />;
}

Standalone Parser

Import and use the markdown parser without the full editor for server-side rendering, static site generation, or browser extensions:

// Import just the parser
import { MarkdownParser } from 'overtype/parser';

// Parse markdown to HTML const html = MarkdownParser.parse('# Hello World\n\nThis is bold text.');

// Use in Node.js for SSR app.get('/preview', (req, res) => { const markdown = req.body.content; const html = MarkdownParser.parse(markdown); res.json({ html }); });

// Use in static site generator const posts = markdownFiles.map(file => ({ content: MarkdownParser.parse(file.content), metadata: file.metadata }));

// Use in browser extension chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.markdown) { const html = MarkdownParser.parse(request.markdown); sendResponse({ html }); } });

Benefits:

  • No DOM dependencies required
  • Smaller bundle when you only need parsing
  • Same markdown rendering as the editor
  • Perfect for headless/server-side use cases

API

Constructor

new OverType(target, options)

Parameters:

  • target - Selector string, Element, NodeList, or Array of elements
  • options - Configuration object (see below)
Returns: Array of OverType instances (always an array, even for single element)

Options

{
  // Typography
  fontSize: '14px',
  lineHeight: 1.6,
  fontFamily: 'monospace',
  padding: '16px',
  
  // Theme - 'solar', 'cave', or custom theme object
  theme: 'solar',
  
  // Custom colors (override theme colors)
  colors: {
    h1: '#e63946',
    h2: '#457b9d',
    // ... any color variable
  },
  
  // Mobile styles (applied at <= 640px)
  mobile: {
    fontSize: '16px',
    padding: '12px',
    lineHeight: 1.5
  },
  
  // Behavior
  autofocus: false,
  placeholder: 'Start typing...',
  value: '',
  
  // Auto-resize
  autoResize: false,      // Auto-expand height with content
  minHeight: '100px',     // Minimum height when autoResize is enabled
  maxHeight: null,        // Maximum height (null = unlimited)
  
  // Native textarea properties
  textareaProps: {
    required: true,
    maxLength: 500,
    name: 'content',
    // Any HTML textarea attribute
  },
  
  // Toolbar
  toolbar: false,         // Enable/disable toolbar
  toolbarButtons: [],     // Custom button array (v2.0)
                          // Defaults to all built-in buttons when toolbar: true

// Syntax highlighting codeHighlighter: null, // Function: (code, lang) => html // Overrides global OverType.setCodeHighlighter() customSyntax: null, // Function: (html) => html // Overrides global OverType.setCustomSyntax() // false = skip the global processor for this editor

// Smart lists smartLists: true, // Enable GitHub-style list continuation on Enter

// Task toggling clickToToggleTasks: false, // Click a task checkbox marker to toggle its state

// Turn off line-level Markdown features (rendered as plain text) disable: [], // 'h1', 'h2', 'h3', 'headings', 'lists', 'tasks', // 'blockquote', 'code', 'hr'

// Spellcheck spellcheck: false, // Enable browser spellcheck (disabled by default)

// Link tooltip transformLinkUrl: (url) => url, // Rewrite the URL shown/opened in the link tooltip (per instance)

// Stats bar showStats: false, // Enable/disable stats bar statsFormatter: (stats) => { // Custom stats format return ${stats.chars} chars | ${stats.words} words; }, // Callbacks onChange: (value, instance) => {}, onKeydown: (event, instance) => {}, onFocus: (event, instance) => {}, onBlur: (event, instance) => {},

// File upload (paste/drop) — see "File Uploads" section below fileUpload: { enabled: true, maxSize: 10 1024 1024, // bytes (default 10MB) mimeTypes: [], // empty = accept all batch: false, // single onInsertFile call per drop onInsertFile: async (file) => ${file.name}, // required: return inserted markdown onRemoveFile: ({ url, filename, file }) => {} // fires when an inserted link is removed } }

Instance Methods

// Get current markdown content
editor.getValue()

// Set markdown content editor.setValue(markdown)

// Get rendered HTML of the current content editor.getRenderedHTML() // With syntax markers (for debugging) editor.getRenderedHTML({ cleanHTML: true }) // Clean HTML without OverType markup editor.getCleanHTML() // Alias for getRenderedHTML({ cleanHTML: true })

// Get the current preview element's HTML editor.getPreviewHTML() // Actual DOM content from preview layer

// Change theme (instance-specific, overrides global theme) editor.setTheme('cave') // Built-in theme name editor.setTheme(customThemeObject) // Custom theme

// Set code highlighter (instance-specific) editor.setCodeHighlighter((code, lang) => highlightedHTML) editor.setCodeHighlighter(null) // Disable for this instance

// Set custom syntax processor (instance-specific) editor.setCustomSyntax((html) => html.replace(/@(\w+)/g, '<span class="mention">$&</span>')) editor.setCustomSyntax(false) // Skip the global processor for this editor editor.setCustomSyntax(null) // Fall back to the global processor

// View modes editor.showNormalEditMode() // Switch to normal edit mode (default) editor.showPlainTextarea() // Switch to plain textarea mode editor.showPreviewMode() // Switch to preview mode

// Focus/blur editor.focus() editor.blur()

// Show or hide stats bar editor.showStats(true) // Show stats editor.showStats(false) // Hide stats

// Check if initialized editor.isInitialized()

// Re-initialize with new options editor.reinit(options)

// Destroy the editor editor.destroy()

Static Methods

// Set global theme (affects all instances without instance themes)
OverType.setTheme('cave')  // Built-in theme
OverType.setTheme(customTheme)  // Custom theme object
OverType.setTheme('solar', { h1: '#custom' })  // Override specific colors

// Set global code highlighter (affects all instances without instance highlighter) OverType.setCodeHighlighter((code, lang) => highlightedHTML) OverType.setCodeHighlighter(null) // Disable global highlighting

// Extend parsing with custom syntax (footnotes, directives, etc.) // IMPORTANT: You must maintain 1-to-1 character alignment - wrap text, don't change it // See: https://panphora.github.io/overtype/examples/custom-syntax.html OverType.setCustomSyntax((html) => { return html.replace(/\[\^(\w+)\]/g, '<span class="footnote">$&</span>'); })

// Note: Instance methods override global settings

// Initialize multiple editors (same as constructor) OverType.init(target, options)

// Initialize with per-element config via data-ot-* attributes // Uses kebab-case: data-ot-show-stats="true" → showStats: true // Accepts a selector, Element, NodeList, or Array of elements OverType.initFromData('.editor', { / defaults / })

// Get instance from a selector, Element, NodeList, or Array // Returns the instance for the first matching element OverType.getInstance(target)

// Destroy all instances OverType.destroyAll()

// Access themes OverType.themes.solar OverType.themes.cave

Keyboard Shortcuts

| Shortcut | Action | |----------|--------| | Cmd/Ctrl + B | Toggle bold | | Cmd/Ctrl + I | Toggle italic | | Cmd/Ctrl + K | Insert link | | Cmd/Ctrl + Shift + 7 | Toggle numbered list | | Cmd/Ctrl + Shift + 8 | Toggle bullet list | | Cmd/Ctrl + ] | Indent current line or selection | | Cmd/Ctrl + | Outdent current line or selection | | Tab / Shift+Tab | Indent or outdent selected lines |

When no text is selected, Tab and Shift+Tab use normal browser focus navigation.

Supported Markdown

Note: Syntax remains visible in edit mode (e.g., bold shows markers). Use preview mode for full rendering.

Web Component

OverType provides a fully-featured native Web Component with Shadow DOM encapsulation and a declarative HTML API.

Quick example:

<overtype-editor
  value="# Hello OverType!"
  theme="solar"
  height="300px"
  toolbar>
</overtype-editor>

📖 Complete Web Component Documentation

Features include:

  • Complete style isolation with Shadow DOM
  • 15 reactive HTML attributes
  • Framework-agnostic (React, Vue, Angular)
  • Built-in themes (Solar, Cave)
  • Custom events API
  • Zero configuration required

Migration from v1.x

Breaking Change: Toolbar API (v2.0)

Old options removed:

// ❌ No longer supported
{
  customToolbarButtons: [...],
  hideButtons: [...],
  buttonOrder: [...]
}

New approach:

// ✅ v2.0: Single explicit array
import { toolbarButtons } from 'overtype';

{ toolbar: true, toolbarButtons: [ toolbarButtons.bold, toolbarButtons.italic, { name: 'custom', icon: '...', action: ({ editor, getValue }) => {} } ] }

If using default toolbar (toolbar: true with no customization), no changes needed.

See examples/custom-toolbar.html for migration examples.

DOM Persistence & Re-initialization

OverType can resume from its own saved DOM, for pages that save themselves (like ClayJS / Hyperclay). Calling new OverType(el) on an element that holds a saved editor keeps the text and rebuilds the toolbar, preview and other UI exactly once.

In a ClayJS page, pass persist: true:

new OverType('#entry', { persist: true, toolbar: true });

With persist, the saved file holds only the text:

<div class="overtype-container" data-theme="solar">
  <div class="overtype-wrapper">
    <textarea class="overtype-input" persist>…your markdown…</textarea>
  </div>
</div>

The textarea gets ClayJS's persist attribute, and the style tag, toolbar, tooltip, stats bar, placeholder and preview are marked clay="editor-ui", so they are never saved and never trigger an autosave. destroy() leaves the container, wrapper and textarea in place, so the editor can be resumed later. When ClayJS live sync merges a change into the textarea, OverType redraws on clay:sync-applied, so collaborators' and agents' edits show without a reload. With autoResize, OverType registers a ClayJS snapshot hook that keeps the editor's inline heights out of the saved file.

Examples

Check the examples folder for complete examples:

  • basic.html - Simple single editor
  • multiple.html - Multiple independent editors
  • custom-theme.html - Theme customization
  • custom-toolbar.html - Custom toolbar buttons (v2.0)
  • dynamic.html - Dynamic creation/destruction

Limitations

Due to the transparent textarea overlay approach, OverType has some intentional design limitations:

Images Not Supported

Images (!alt) are not rendered. Variable-height images would break the character alignment between textarea and preview.

Monospace Font Required

All text must use a monospace font to maintain alignment. Variable-width fonts would cause the textarea cursor position to drift from the visual text position.

Fixed Font Size

All content must use the same font size. Different sizes for headers or other elements would break vertical alignment.

Visible Markdown Syntax

All markdown formatting characters remain visible (e.g., bold shows the asterisks). This is intentional - hiding them would break the 1:1 character mapping.

Links Require Modifier Key

Links are clickable with Cmd/Ctrl+Click only. Direct clicking would interfere with text editing since clicks need to position the cursor in the textarea.

These limitations are what enable OverType's core benefits: perfect native textarea behavior, tiny size, and zero complexity.

Transforming Link URLs

When your cursor is inside a text, OverType shows a tooltip to open the link. If you edit a document at a different path or domain than where it's published, relative URLs can point to the wrong place. Pass transformLinkUrl to rewrite the URL shown and opened by the tooltip, per instance. Your markdown text and the rendered preview are left untouched.

const urlPreviewRegExp = [[/^\//, '/preview/'], [/www\./, '']];

const [editor] = new OverType('#editor', { transformLinkUrl: (url) => urlPreviewRegExp.reduce((value, re) => value.replace(...re), url) });

The transformed URL is passed through OverType's URL sanitizer before opening, so the tooltip can never open a dangerous scheme (javascript:, data:, and similar). If your function throws or returns a non-string, the original URL is used.

Recipes

OverType stays small by leaving optional features to the host app and exposing the public API needed to build them (editor.textarea, onChange, getValue/setValue).

Development

# Install dependencies
npm install

Development build with watch

npm run dev

Production build

npm run build

Run tests

npm test

Check bundle size

npm run size

Browser Support

  • Chrome 62+
  • Firefox 78+
  • Safari 16+
  • Edge (Chromium)
Requires support for:
  • CSS Custom Properties
  • ES6 features
  • Lookbehind assertions in RegExp (for strikethrough parsing)

Architecture

OverType uses a unique invisible textarea overlay approach:

  1. Two perfectly aligned layers:
- Invisible textarea (top) - handles input and cursor - Styled preview div (bottom) - shows formatted markdown
  1. Character-perfect alignment:
- Monospace font required - No size changes in styling - Syntax markers remain visible
  1. Single source of truth:
- Textarea content drives everything - One-way data flow: textarea → parser → preview

Data Attribute Configuration

Use OverType.initFromData() to configure multiple editors via HTML data attributes:

<div class="editor" data-ot-toolbar="true" data-ot-theme="cave"></div>
<div class="editor" data-ot-auto-resize="true" data-ot-min-height="200px"></div>
<div class="editor" data-ot-show-stats="true" data-ot-placeholder="Write here..."></div>

<script> OverType.initFromData('.editor', { fontSize: '14px' }); // defaults </script>

Uses kebab-case attributes that convert to camelCase options (e.g., data-ot-show-stats → showStats).

Native textarea attributes (required, name, etc.)

Set textareaProps from HTML in two ways:

<!-- One attribute per prop: data-ot-textarea-<attr> -->
<div class="editor" data-ot-textarea-required data-ot-textarea-name="message"></div>

<!-- Or the whole object as JSON --> <div class="editor" data-ot-textarea-props='{"required":true,"maxLength":500,"name":"message"}'></div>

Both put a real required/name/etc. attribute on the underlying