DEV Community

Cover image for I Built a Markdown Converter That Finally Doesn’t Suck (And It’s Free)
Muhammad Usman
Muhammad Usman

Posted on

I Built a Markdown Converter That Finally Doesn’t Suck (And It’s Free)

The Problem That Drove Me Crazy

You know that feeling when you copy text from Medium or dev.to, and it pastes with a rainbow of background colors, broken formatting, and mysterious inline styles? Yeah, I got tired of that.

I needed a tool that could take messy web content and give me clean Markdown. Simple, right?

Turns out, every converter I tried was either broken, ugly, or missing critical features. Some stripped ALL formatting. Others kept the garbage backgrounds. None handled images in links properly.

So I built my own. And honestly? It turned out way better than I expected.

Official Website Link for Rich Text To Markdoen Converter

Code is OpenSource:

What I Built

100% Free Markdown Converter - a real-time, intelligent converter that transforms any web content into perfect Markdown. No server, no API calls, just pure client-side magic.

The Core Features I'm Most Proud Of

1. Smart Paste That Actually Works

This was the hardest part to nail. When you paste content, my converter:

  • Strips all the garbage: Background colors, inline styles, CSS classes - gone
  • Keeps the structure: Headings, bold, italic, links, images - preserved
  • Handles edge cases: Images in links? Fixed. Anchor-linked headings? Cleaned. Code blocks with extra spacing? Trimmed perfectly.

I wrote a custom HTML cleaner that walks through every element and makes intelligent decisions about what to keep. It's not using some generic library - this is custom logic built from testing hundreds of real-world websites.

2. Real-Time Markdown Preview

Every keystroke updates the Markdown output instantly. No "Convert" button, no loading spinners. Type on the left, see Markdown on the right.

I implemented this with a custom HTML-to-Markdown parser that handles:

  • All heading levels (H1-H6)
  • Nested lists (both ordered and unordered)
  • Tables with proper formatting
  • Code blocks with intelligent whitespace handling
  • Blockquotes that span multiple lines

3. The Image Intelligence I'm Really Proud Of

Here's a problem nobody else solved properly: websites wrap images in anchor tags for lightbox effects. Every converter I tested either:

  • Kept the wrapper link (useless in Markdown)
  • Broke the image entirely
  • Generated malformed syntax

My solution: I detect if a link contains ONLY an image (no text), and extract just the image. If there's text too, it keeps the link structure. Simple logic, massive improvement.

// This took forever to get right
if (tag === "a") {
  const imgEl = el.querySelector('img');
  const hasText = el.textContent.trim().length > 0;

  if (imgEl && !hasText) {
    // Just output the image, skip the link wrapper
    return `![${alt}](${src})\n\n`;
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Heading Link Stripping

Another annoying thing, dev.to, Medium, and other platforms wrap every heading in an anchor link for deep linking. When you convert, you get:

### [https://example.com#section-name](url)My Actual Heading
Enter fullscreen mode Exit fullscreen mode

Completely broken.

My fix: I walk up the DOM tree when processing links. If a link is inside a heading element, I return only the text content. Clean headings, every time.

5. Code Block Perfection

Code blocks are deceptively hard. Paste code from GitHub, and it has extra newlines. From Stack Overflow? Different formatting. From your IDE? Yet another style.

I built smart trimming:

  • Removes leading blank lines
  • Removes trailing blank lines
  • Preserves internal spacing (critical for Python, YAML, etc.)

Sounds simple, but getting this right took testing dozens of edge cases.

The UX Details That Make It Feel Premium

Professional Design Language

I didn't want another boring developer tool. I studied modern SaaS apps (Linear, Notion, Vercel) and built a dark theme with:

  • Subtle gradients on the logo icon
  • Smooth micro-animations on buttons and modals
  • Proper elevation with layered shadows
  • Consistent spacing using an 8px grid system
  • Refined typography with -0.02em letter spacing on headings

Modal System Over Browser Prompts

Instead of ugly prompt() dialogs for links/images/tables, I built a proper modal system:

  • Backdrop blur effect
  • Smooth slide-up animation with cubic-bezier easing
  • Keyboard shortcuts (Enter to submit, Escape to close)
  • Focus management (automatically focuses the right input)
  • Form validation with helpful error messages

Resizable Panels

Want more editor space? Drag the divider. It changes to blue on hover, shows a col-resize cursor, and updates panel widths smoothly.

// Simple but effective
const percentage = ((e.clientX - containerRect.left) / containerRect.width) * 100;
if (percentage > 20 && percentage < 80) {
  panels[0].style.flex = `0 0 ${percentage}%`;
  panels[1].style.flex = `0 0 ${100 - percentage}%`;
}
Enter fullscreen mode Exit fullscreen mode

Smart Empty States

Instead of blank panels screaming at you, I added friendly empty states:

  • Large emoji icon (📝)
  • Clear message about what to do
  • Helpful tip about pasting

Live Statistics

Character, word, and line counts update in real-time. Useful for hitting article length targets or checking document size.

Toast Notifications

Every action gives feedback:

  • Success toasts (green) for copy/download
  • Error toasts (red) for validation failures
  • Smooth slide-up animation from bottom-right
  • Auto-dismiss after 3 seconds

Technical Decisions I Made

Why No Framework?

Vanilla JavaScript. No React, no Vue, no build step.

Why? Because:

  1. Performance: Zero framework overhead means instant load
  2. Simplicity: 500 lines of JS instead of 5000 with dependencies
  3. Maintainability: Anyone can read and understand the code
  4. File size: Single HTML file under 50KB

ContentEditable for the Win

I used contenteditable for the editor instead of a textarea because:

  • Native rich text support (bold, italic, etc.)
  • Built-in undo/redo via document.execCommand
  • Paste handling with full HTML access
  • Better UX for formatting tools

The trick was handling paste events properly to clean content before insertion.

Custom HTML-to-Markdown Parser

I could've used Turndown or similar libraries, but they don't handle edge cases well:

  • Background colors in pasted content
  • Anchor-wrapped headings
  • Image-only links
  • Excessive whitespace in code blocks

Building custom gave me full control over every conversion scenario.

Client-Side Only Architecture

Everything runs in your browser. No server, no API, no database.

Benefits:

  • Privacy: Your content never leaves your device
  • Speed: No network latency
  • Reliability: Works offline (after initial load)
  • Cost: Zero hosting/bandwidth costs

CSS Variables for Theming

I set up a complete design system with CSS variables:

:root {
  --bg-primary: #0a0a0a;
  --bg-secondary: #141414;
  --accent-primary: #3b82f6;
  --text-primary: #fafafa;
  /* ...and 15 more */
}
Enter fullscreen mode Exit fullscreen mode

This makes theming trivial. Want to add light mode? Just override the variables.

Features Breakdown

Text Formatting

  • Bold (⌘B) - Wraps in **text**
  • Italic (⌘I) - Wraps in *text*
  • Underline (⌘U) - Uses <u>text</u>
  • Strikethrough - Converts to ~~text~~

Structure Elements

  • Headings (H1-H6) - Full support with proper # syntax
  • Paragraphs - Smart spacing
  • Line breaks - Preserved correctly
  • Horizontal rules - Clean --- output

Lists

  • Bullet lists with proper indentation
  • Numbered lists with sequential numbering
  • Nested lists (though Markdown has limits here)

Rich Content

  • Links - Clean [text](url) format
  • Images - Proper ![alt](url) syntax
  • Code blocks - Fenced with triple backticks
  • Inline code - Wrapped in single backticks
  • Blockquotes - Multi-line support with > prefix
  • Tables - Full GFM-style tables with headers

Actions

  • Copy (⌘S) - Clipboard copy with toast confirmation
  • Download - Saves as .md file with timestamp
  • Clear - Wipes everything (with confirmation)
  • Undo (⌘Z) - Full history support
  • Redo (⌘⇧Z) - Redo your undos

The Challenges I Overcame

1. Paste Event Hell

Handling paste is surprisingly complex. Different sources provide data differently:

  • Some send text/html with full DOM
  • Others send text/plain only
  • Some have broken HTML with unclosed tags
  • Many have inline styles that break everything

My solution: Always prefer HTML if available, clean it aggressively, then insert.

2. Selection Management for Modals

When you click "Insert Link", you need to remember where the cursor was. But opening a modal loses focus!

I implemented selection save/restore:

function saveSelection() {
  const sel = window.getSelection();
  if (sel.rangeCount > 0) {
    savedSelection = sel.getRangeAt(0);
  }
}

function restoreSelection() {
  if (savedSelection) {
    const sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(savedSelection);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now modals work seamlessly without losing your place.

3. Code Block Whitespace

This one drove me nuts. Pasted code often has:

  • 3-4 blank lines at the start
  • 2-3 blank lines at the end
  • But intentional blank lines in the middle (important!)

Can't just .trim() because that removes internal spacing too.

My solution: Loop through lines, trim from start until non-empty, trim from end until non-empty, preserve everything else.

4. Table Cell Content with Newlines

Tables in Markdown can't have newlines in cells. But pasted tables often do!

I replace newlines with spaces during table processing:

const cells = Array.from(row.querySelectorAll("td, th"))
  .map(c => processNode(c).trim().replace(/\n/g, ' '));
Enter fullscreen mode Exit fullscreen mode

Simple fix, huge impact.

5. Responsive Design Without Breaking Features

Making this work on mobile was tricky:

  • Toolbar buttons need to be accessible
  • Panels need to stack vertically
  • Modals need to fit small screens
  • Touch scrolling needs to feel native

I used flexbox, careful media queries, and -webkit-overflow-scrolling: touch to nail the mobile experience.

Use Cases I Designed For

1. Cross-Posting Blog Articles

Problem: You publish on Medium, want to post to dev.to
Solution: Copy from Medium, paste here, download Markdown, post to dev.to

2. GitHub README Creation

Problem: Writing Markdown syntax manually is tedious
Solution: Write in the editor with formatting buttons, copy the output

3. Cleaning Legacy HTML

Problem: Old content with inline styles and deprecated tags
Solution: Paste the mess, get clean modern Markdown

4. Documentation Writing

Problem: Need to create docs with code examples, tables, images
Solution: Use the formatting tools to build structured content

5. Quick Notes

Problem: Want to jot down formatted notes in Markdown
Solution: Type naturally, copy Markdown when done

Performance Optimizations

1. Debouncing Updates

Real-time conversion on every keystroke could lag. But it doesn't, because I update synchronously - the parsing is fast enough (<5ms for typical content).

2. Efficient DOM Manipulation

I use documentFragment and batch operations where possible to minimize reflows.

3. CSS Hardware Acceleration

Animations use transform and opacity - GPU-accelerated properties that stay smooth at 60fps.

4. No Memory Leaks

Event listeners are properly managed. Selection objects are cleared when not needed.

What I Learned Building This

1. ContentEditable is Weird

contentEditable has quirks. Different browsers handle formatting differently. Some create <b> tags, others <strong>. Had to normalize everything.

2. Markdown Has Limitations

Markdown can't represent everything HTML can. Merged table cells? Nope. Nested divs? Nope. Had to make smart compromises.

3. User Testing is Critical

I thought my first version was great. Then I tested on real websites and found:

  • Background colors everywhere (missed in cleaning)
  • Images wrapped in links (broke the output)
  • Heading anchor links (created garbage)

Real content is messier than test content.

4. Details Matter

The difference between a good tool and a great tool is 100 small details:

  • Tooltip positioning
  • Button hover states
  • Empty state messaging
  • Error feedback
  • Keyboard shortcuts
  • Touch gestures

I sweated all of them.

5. Sometimes Vanilla is Best

Modern frameworks are amazing, but for a focused tool like this, vanilla JS was perfect. Faster development, better performance, easier maintenance.

The Numbers

Development Time: ~20 hours across 3 days
Lines of Code:

  • JavaScript: ~500 lines
  • CSS: ~800 lines
  • HTML: ~200 lines

Total File Size: 48KB (uncompressed)
Dependencies: Zero
Browser Support: Chrome 90+, Firefox 88+, Safari 14+

Performance:

  • Initial load: <100ms
  • Paste to conversion: <50ms (for 5000 word articles)
  • Memory usage: ~15MB (typical)

Features I'm Planning

Short Term

  • [ ] Syntax highlighting for code blocks (using Prism.js)
  • [ ] Dark/light theme toggle (already have CSS variables ready)
  • [ ] Import from URL (fetch and convert any webpage)
  • [ ] Markdown preview (rendered HTML view)

Medium Term

  • [ ] Browser extension (convert pages with a click)
  • [ ] Batch conversion (multiple files at once)
  • [ ] Custom shortcuts (user-definable hotkeys)
  • [ ] Export to PDF (via print styles)

Long Term

  • [ ] Collaboration (share and edit together)
  • [ ] Cloud sync (optional, via localStorage backup)
  • [ ] Mobile apps (React Native wrapper)
  • [ ] API (programmatic access)

Technical Stack Deep Dive

The Conversion Engine

At the heart of this is a recursive DOM walker:

function processNode(node) {
  let result = "";
  for (let child of node.childNodes) {
    if (child.nodeType === Node.TEXT_NODE) {
      result += child.textContent;
    } else if (child.nodeType === Node.ELEMENT_NODE) {
      result += processElement(child);
    }
  }
  return result;
}
Enter fullscreen mode Exit fullscreen mode

This walks every node in the pasted HTML, makes decisions about each element, and builds the Markdown string recursively.

The Cleaning System

Before conversion, I clean the HTML:

function cleanElement(element) {
  const allowedTags = ['p', 'br', 'strong', 'b', 'em', 'i', 'u', 's', ...];

  element.querySelectorAll('*').forEach(el => {
    if (!allowedTags.includes(el.tagName.toLowerCase())) {
      // Replace with children
      const parent = el.parentNode;
      while (el.firstChild) {
        parent.insertBefore(el.firstChild, el);
      }
      parent.removeChild(el);
    }

    // Strip all style attributes
    el.removeAttribute('style');
    el.removeAttribute('class');
    // ... keep only essential attributes
  });
}
Enter fullscreen mode Exit fullscreen mode

This ensures only semantic HTML makes it through.

The Modal System

I built a reusable modal system:

function showModal(modalId, onSubmit) {
  saveSelection();
  document.getElementById(modalId).classList.add('show');
  // Focus management, keyboard handlers, etc.
}
Enter fullscreen mode Exit fullscreen mode

Each modal (link, image, table) uses this base with custom submit handlers.

Design Philosophy

1. Zero Configuration

No settings menu, no preferences. It just works. Smart defaults for everything.

2. Instant Feedback

Every action shows immediate results. No loading states, no waiting.

3. Forgiving UX

Made a mistake? Undo it. Pasted wrong? Clear and try again. Modals have cancel buttons. Confirmations prevent data loss.

4. Keyboard-First

Power users can do everything without touching the mouse. Shortcuts for all common actions.

5. Beautiful by Default

Good design isn't decoration - it's clarity. Every pixel serves a purpose.

Why This Matters

Better tools make better work possible.

Before building this, I was:

  • Manually cleaning HTML in VS Code
  • Writing Markdown syntax by hand
  • Copying formatting styles one element at a time

Now I paste and move on. This saves me 10-15 minutes per article. For someone who writes 3-4 articles a week, that's an hour saved weekly.

And it's not just time - it's mental energy. Not having to think about formatting means more energy for writing.

Open Source Considerations

I'm considering open sourcing this because:

Pros:

  • Community contributions could add features faster
  • Others could learn from the code
  • Increased trust (source is visible)

Cons:

  • Support burden (issues, PRs, questions)
  • Feature creep (everyone wants their thing added)
  • Competitors could clone it

Still deciding. Thoughts?

Try It Yourself

The best way to understand what I built is to use it:

  1. Open the app
  2. Go to any article on Medium, dev.to, or your blog
  3. Copy a few paragraphs with images and code
  4. Paste into the converter
  5. Watch the magic happen

You'll see instantly why I spent 20 hours on this. The little details add up to a tool that just works.

Final Thoughts

Building this taught me that sometimes you need to build the tool you wish existed.

Could I have used an existing converter? Sure. But none of them handled my use cases properly. So I built one that does.

The result is something I use daily, and I'm genuinely proud of how it turned out. From the smooth animations to the smart paste handling to the clean Markdown output - every detail was intentional.

This is what happens when you care about craft.

Not just making something that works, but making something that feels good to use. Something fast, beautiful, and reliable.

That's what I built. And honestly? I think I nailed it.

Built with ⚡ and way too much attention to detail

Want to see the code? Have feedback? Found a bug? I'm all ears. This is v1.0, and it's only getting better from here.

Now go convert something and see for yourself.

Which of these new features are you most excited to use?

— — — — — — — — — — — — — — — — — — — — — — —

Did you learn something good today?
Then show some love. 🫰
© Muhammad Usman
WordPress Developer | Website Strategist | SEO Specialist
Don’t forget to subscribe to Developer’s Journey to show your support.
Available for freelance projects on Upwork

Developer's Journey

Top comments (1)

Collapse
 
web_dev-usman profile image
Muhammad Usman

I want you to test the tool and let me know if there are any bugs in tool