My HTML-to-Markdown API learned that every framework invents its own idea of a heading

I built the HTML-to-Markdown converter expecting a boring job: walk the DOM, emit # for <h1>, ## for <h2>, done. The first real-world test was a Next.js docs site, and it came back as a wall of bold text with zero heading structure.

The culprit: Tailwind. The site styled <div class="text-2xl font-bold mb-4"> as its visual headings. No <h1> tags anywhere. Semantically it was a disaster, visually it was a perfectly good docs page. My converter saw divs and shrugged.

The fix wasn't smarter HTML parsing — it was admitting that "heading" is a visual concept, not a tag concept. I added a heuristic pass: before the structural walk, scan for elements whose computed class names match common heading patterns (text-xl, text-2xl, text-3xl, font-bold, font-semibold, mb- spacing tokens) and promote them. It's ugly. It's fragile. It works on maybe 80% of Tailwind sites.

Then I hit a Bootstrap site that used <h1 class="h3"> — a real heading tag, visually demoted. My heuristic trusted the tag. The output had an # where the page showed a small subheading. I had to add the reverse check: if a real heading tag carries a class that visually demotes it (h1-h6 Bootstrap classes), demote in the output too.

The lesson: HTML-to-Markdown isn't a parsing problem, it's a translation problem between two ontologies. The HTML author thought in visual hierarchy. The Markdown consumer thinks in document structure. They only agree by accident. Every CSS framework is a new dialect of "what looks like a heading."

I ended up shipping the heuristic pass behind an opt-in flag, default off, because it's wrong often enough that I don't want to surprise anyone. If your pipeline ingests marketing sites and docs built with utility CSS, turn it on. If you ingest semantic HTML from a CMS, leave it off.

Packaged here: https://x402.freeq.one/tools/html_to_markdown.html

Anyone else fighting the Tailwind-heading tax? I'd love to hear what heuristics you've found that don't feel like guesswork.

Story originally reported by Dev.to. View at Dev.to →
← Back to all news