Tutorials 16 min read

Write H₂O in a Note and GitHub Crosses Out the 2: Highlight, Subscript, Superscript, and Underline Across Nine Renderer Setups

MMNMNOTE
markdownsyntaxsubscriptsuperscripthighlightunderlinegithubpandoc
Updated October 28, 2026

You write a chemistry note. The molecule is water, so you type H~2~O — the standard subscript syntax the pandoc-flavoured editor you use accepts without complaint. You commit the file. You open the README on GitHub. The 2 is struck through. "H", a deleted "2", "O."

That is not a rendering glitch. It is a spec collision. The GitHub Flavored Markdown specification §6.5 states, verbatim: "Strikethrough text is any text wrapped in a matching pair of one or two tildes ()."1 The pandoc User's Guide states, equally verbatim: "subscripts may be written by surrounding the subscripted text by ~ characters. Thus, for example, H2~O is a liquid."2 Same characters, opposite meanings.

This collision is the sharpest of several that affect the four inline marks note-writers most often reach for: highlight, subscript, superscript, and underline. About 1.79 million views across five Stack Overflow questions measure the demand.3 None of the four is defined in CommonMark — the shared baseline nearly every renderer agrees on. Each was invented by a different tool. This reference measures the behaviour so you do not have to discover the collisions yourself.


How each of the four marks behaves across nine renderers

None of highlight, subscript, superscript, or underline is in CommonMark — the shared baseline that nearly every renderer agrees on. Each was added by a different flavour for a different audience. The table below measures ten typed forms across nine setups run on 2026-10-28: commonmark.js 0.31.2, micromark 4.0.3 + GFM, markdown-it 15 default, markdown-it with mark/sub/sup/ins plugins, pandoc 3.12.1 markdown, pandoc gfm, pandoc commonmark, Python-Markdown 3.11.2, and GitHub's own POST /markdown API.

Typed in the notecommonmark.jsmicromark + GFMmarkdown-it defaultmarkdown-it + pluginspandoc markdownpandoc gfmpandoc commonmarkPython-MarkdownGitHub API
==highlight==literalliteralliteralhighlightliteralliteralliteralliteralliteral
H~2~Oliteralstruck-through 2literalsubscript 2subscript 2literalliteralliteralstruck-through 2
x^2^literalliteralliteralsuperscriptsuperscriptliteralliteralliteralliteral
~single~literalstrikethroughliteralsubscriptsubscriptliteralliteralliteralstrikethrough
~~strike~~literalstrikethrough (<del>)strikethrough (<s>)strikethrough (<s>)strikethrough (<del>)strikethrough (<del>)literalliteralstrikethrough (<del>)
__underline__boldboldboldboldboldboldboldboldbold
<u>under</u>underlineunderlineunderlineunderlineunderlineunderlineunderlineunderlinetag removed, plain text
<ins>under</ins>underlineunderlineunderlineunderlineunderlineunderlineunderlineunderlineunderline
<mark>x</mark>keptkeptkeptkeptkeptkeptkeptkeptkept
<sub>2</sub> / <sup>2</sup>keptkeptkeptkeptkeptkeptkeptkeptkept

Three findings carry the rest of this piece. First, H~2~O is subscript in two setups and strikethrough in two others — the same characters mean opposite things in different specs. Second, __underline__ renders as bold in all nine setups; the most-viewed of these five Stack Overflow questions has no Markdown answer. Third, GitHub removes <u> from rendered HTML while keeping <ins>, <mark>, <sub>, and <sup>.


Highlight: ==text== reaches one setup without a plugin

==text== is highlight in Obsidian's renderer and in markdown-it with the mark plugin loaded. Every other tested setup — commonmark.js, micromark + GFM, pandoc in all three modes, Python-Markdown, and GitHub's API — prints it as literal equals signs on both sides of the text. Use <mark>text</mark> when the note will ever leave Obsidian or a markdown-it-mark-equipped editor.

==This sentence is highlighted in Obsidian and markdown-it-mark.==
==This sentence shows as ==literal equals signs== everywhere else.==
<mark>This sentence is highlighted in all nine setups.</mark>

Obsidian documents the extension thoroughly and in 1.14 added colour variants: "Highlights support six colors. Add a color emoji immediately after the opening ==:" followed by six colour emoji (🔴 🟠 🟡 🟢 🔵 🟣).4 That is intentional and accurate for Obsidian's own renderer. The caveat lives in the same repository, one click away: "Some of these, such as internal links, comments, and highlights, are specific to Obsidian. Other Markdown apps may show them as plain text."5

pandoc supports a non-default extension (-f markdown+mark) that produces <mark> for ==text== — but this is not the default pandoc mode, and gfm+mark is rejected as unsupported. pandoc's own markdown-to-gfm export path converts {.mark} spans to <span class="mark">, which GitHub then strips of its class attribute: the highlight is silently lost at the class-stripping step. Obsidian's ==🔴Important== travels as the literal string ==🔴Important== plus an emoji in every other renderer.6

<mark>text</mark> survives all nine setups. It is the only portable highlight path in this table.


Subscript: H2O is chemistry in pandoc and deletion on GitHub

The single tilde marks a subscript in pandoc markdown and in markdown-it with the sub plugin. The same character marks a strikethrough in GitHub Flavored Markdown and micromark + GFM. The two specs do not agree, so H~2~O becomes H, struck-through 2, O when a pandoc note lands on GitHub. The receipt measurement confirms the split: subscript in 2 of 9 setups, strikethrough in 2 of 9, literal text in the remaining 5.

H~2~O           <!-- subscript in pandoc markdown + markdown-it+sub  -->
                <!-- struck-through 2 on GitHub and micromark+GFM    -->
H<sub>2</sub>O  <!-- subscript in all nine setups                     -->

pandoc's User's Guide is explicit about why the tilde requires surrounding spaces to avoid triggering subscript accidentally: "(This is to prevent accidental superscripting and subscripting through the ordinary use of ~ and ^, which must be common in some contexts, for instance, in expressions like ~0.5 or a^n.)"2 The very collision pandoc's spec anticipates — accidental tildes interpreted as markup — is precisely what GFM formalises.

The markdown-it-sub README makes its alignment clear: "Markup is based on [pandoc] definition."7 Any markdown-it-powered editor that loads the sub plugin will accept H~2~O as subscript — and the same note, copied to GitHub, strikes through the 2.

pandoc handles its own round-trip gracefully. Running -f markdown -t gfm converts H~2~O to H<sub>2</sub>O automatically:

pandoc -f markdown -t gfm input.md -o output.md
# H~2~O in input becomes H<sub>2</sub>O in the GFM output

If you author in pandoc markdown and need to share on GitHub, use pandoc's export rather than pasting the ~-marked source directly. For notes that must travel without conversion, <sub>2</sub> survives all nine setups.


Superscript: x^2^ is a pandoc extension, not a standard

x^2^ is a superscript in pandoc markdown and in markdown-it with the superscript plugin loaded. It prints as the literal characters x^2^ in commonmark.js, micromark, pandoc gfm, pandoc commonmark, Python-Markdown, and GitHub's API. There is no collision equivalent to the tilde: x^2^ fails silently as literal text rather than altering the note's meaning.

x^2^            <!-- superscript in pandoc markdown + markdown-it+sup -->
                <!-- literal: x^2^  in all other setups               -->
x<sup>2</sup>   <!-- superscript in all nine setups                    -->

Unlike the tilde, the caret does not carry a conflicting meaning in any other common extension. The only cost of using x^2^ in a multi-renderer note is visual — the reader sees x^2^ rather than x². For chemistry or mathematical notation in notes that stay within a pandoc pipeline or a plugin-equipped markdown-it editor, x^2^ is readable. In any other context, use <sup>.

<sup>2</sup> is kept in all nine setups, including GitHub, which keeps <sup> despite removing <u> and stripping class attributes from spans. It takes a few more characters and reliably produces a raised numeral everywhere tested.


Underline: the mark with no portable Markdown form

No tested renderer underlines __text__ — all nine render it as bold, because double underscore is a bold delimiter in CommonMark. <u>text</u> is stripped to plain text by GitHub. <ins>text</ins> survives all nine setups and renders as underlined, but its HTML semantic meaning is "inserted text," not underlined text.

__underline__    <!-- bold in all 9 setups; underlines nothing      -->
<u>under</u>     <!-- underline in 8/9; tag removed by GitHub        -->
<ins>under</ins> <!-- underline in all 9 (insertion semantics)      -->

The most-viewed of the five Stack Overflow questions — "Get underlined text with Markdown," 876,126 views and 521 votes — has no Markdown answer.3 Our earlier post on Markdown for readers who don't code notes one line: Discord reads __underline__ as underline where standard Markdown reads it as bold. Discord's behaviour is the exception. Every setup measured here renders __underline__ as bold.

pandoc provides its own underline extension — [Underline]{.underline} — which produces <u> in pandoc's HTML output.6 It is a pandoc-only form; every other setup in the table prints it as literal bracket-text. The Markdown cheat sheet covers ~~strikethrough~~ as GFM syntax; none of the four extra marks discussed here has a section there — this measured table is the gap it does not fill.

GitHub's removal of <u> comes from its sanitization layer, not from the GFM tagfilter. The GFM specification §6.11 tagfilter lists nine blocked tags (title, textarea, style, xmp, iframe, noembed, noframes, script, plaintext) and states: "All other HTML tags are left untouched."8 <u> is not in that list. A separate sanitization step handles the removal — documented in the github/markup library: "The HTML is sanitized, aggressively removing things that could harm you and your kin — such as script tags, inline-styles, and class or id attributes."9 The html-pipeline v2.14.3 sanitization allowlist explicitly keeps ins, del, sup, sub, s, mark but not u.10

The WHATWG HTML Living Standard explains why <u> has this narrow role: "Authors are encouraged to avoid using the u element where it could be confused for a hyperlink."11 The standard lists alternatives for stress emphasis (em), key terms (b), book titles (cite), defining instances (dfn), and keywords (code). Underline is reserved for non-textual annotation such as a misspelled word or a proper name in Chinese text.

<ins>text</ins> is the closest portable underline. It underlines in all nine setups and survives GitHub. Accept its insertion semantics — or use emphasis.


The four-line portable rule

Use <mark> for highlight, <sub> for subscript, <sup> for superscript, and <ins> for underline if you need the visual form and accept the insertion meaning. Never use single tildes in a note that will be read on GitHub. These four lines cover every setup tested: the HTML tags pass through all nine renderers; the Markdown extensions do not.

The rule, stated as code:

<!-- Highlight -->
<mark>text</mark>        ✓ all 9 setups  |  ==text==  ✗ 8/9 (literal)

<!-- Subscript -->
H<sub>2</sub>O           ✓ all 9 setups  |  H~2~O     ✗ 7/9 (strikethrough on 2)

<!-- Superscript -->
x<sup>2</sup>            ✓ all 9 setups  |  x^2^      ✗ 7/9 (literal)

<!-- Underline — no semantic-neutral portable form -->
<ins>text</ins>          ✓ all 9 setups (insertion meaning)
<u>text</u>              ✗ 1/9 (stripped by GitHub)
__text__                 ✗ 0/9 (bold everywhere)

The meta-rule: when the note must read correctly in any renderer, write the HTML tag, not the Markdown extension. The extensions are shorthand for a single controlled environment — within Obsidian, within a pandoc pipeline, within a markdown-it editor configured with the right plugins. The moment the note travels, the HTML tags are the portable choice.


Frequently asked questions

How do I write H₂O in Markdown so it displays correctly everywhere?

Use H<sub>2</sub>O. The HTML tag renders as H₂O in all nine setups tested, including GitHub. H~2~O renders as subscript in pandoc and markdown-it + sub plugin, but as H, struck-through 2, O on GitHub and micromark + GFM — the GFM specification defines a single tilde pair as strikethrough. <sub> is the only form that does not collide.

Why does GitHub cross out the 2 when I write H2O?

GitHub Flavored Markdown §6.5 defines a single tilde pair as strikethrough: "Strikethrough text is any text wrapped in a matching pair of one or two tildes (~)."1 pandoc uses the same characters for subscript. A note that uses H~2~O in a pandoc-flavoured editor and is then pushed to GitHub will have its 2 rendered as deleted text. Replace ~2~ with <sub>2</sub> before sharing on GitHub.

How do I highlight text so it works on GitHub?

==text== highlights in Obsidian and in markdown-it with the mark plugin; it prints as literal equals signs everywhere else. For GitHub compatibility, use <mark>text</mark>. The <mark> tag is kept in GitHub's rendered HTML — it is not in the tagfilter or sanitization blocklist.

How do I underline text in Markdown?

There is no portable Markdown underline. __text__ is bold in all nine setups — it underlines in nothing. <u>text</u> is stripped to plain text by GitHub. <ins>text</ins> survives all nine setups and renders as underlined, but its HTML semantic meaning is "inserted text," not emphasis. Use *text* or _text_ for emphasis unless you specifically mean a textual insertion.

Does ==highlight== work on GitHub?

No. GitHub's Markdown renderer does not support ==highlight==. It prints the equals signs as literal characters on each side of the text. Use <mark>text</mark> for inline highlight on GitHub; the tag passes through GitHub's sanitization layer.

How do I write superscripts that survive every renderer?

Use <sup>text</sup>. It is kept in all nine setups including GitHub, which allows <sup> even while it removes <u>. x^2^ is pandoc markdown and the markdown-it-sup plugin only; it prints as literal characters on GitHub, commonmark.js, pandoc gfm, pandoc commonmark, and Python-Markdown.


A note typed in one editor and read in another carries the risk of silent meaning changes — a chemistry formula that looks like a deletion, a bold word where underline was intended. The HTML tags for these four marks are not a workaround; they are the portable Markdown. The extensions are shorthand for a single controlled environment.


If the notes you write should stay readable wherever you open them, mnmnote.com was built for plain-text portability.


Footnotes

  1. GitHub Flavored Markdown Spec 0.29, §6.5 Strikethrough (extension), GitHub Inc., 2019-04-06. https://github.github.com/gfm/#strikethrough-extension- — Wayback: https://web.archive.org/web/20261028012336/https://github.github.com/gfm/ ↩ ↩2

  2. John MacFarlane, pandoc User's Guide, Superscripts and subscripts, pandoc 3.12.1, fetched 2026-10-28. https://pandoc.org/MANUAL.html#superscripts-and-subscripts — Wayback: https://web.archive.org/web/20261028012355/https://pandoc.org/MANUAL.html ↩ ↩2

  3. Stack Exchange API 2.3, accessed 2026-10-28. Five questions: q/3003476 "Get underlined text with Markdown" 876,126 views / 521 votes; q/15155778 "Superscript in markdown (Github flavored)?" 460,648 / 649; q/25104738 "text highlight in markdown" 324,953 / 178; q/15757695 "Producing subscripts in R markdown" 100,205 / 62; q/43359091 "how to write the leading superscript and subscript in Markdown syntax?" 25,297 / 10. Total: 1,787,229 views. ↩ ↩2

  4. obsidianmd/obsidian-help, Basic formatting syntax, commit eef5464094 "Obsidian 1.14", 2026-10-06. https://raw.githubusercontent.com/obsidianmd/obsidian-help/master/en/Editing%20and%20formatting/Basic%20formatting%20syntax.md — Wayback: https://web.archive.org/web/20261028012412/https://raw.githubusercontent.com/obsidianmd/obsidian-help/master/en/Editing%20and%20formatting/Basic%20formatting%20syntax.md ↩

  5. obsidianmd/obsidian-help, Obsidian Flavored Markdown, fetched 2026-10-28. https://raw.githubusercontent.com/obsidianmd/obsidian-help/master/en/Obsidian%20Flavored%20Markdown.md — Wayback: https://web.archive.org/web/20261028012418/https://raw.githubusercontent.com/obsidianmd/obsidian-help/master/en/Obsidian%20Flavored%20Markdown.md ↩

  6. John MacFarlane, pandoc User's Guide, Underline and Highlighting sections, pandoc 3.12.1, fetched 2026-10-28. https://pandoc.org/MANUAL.html ↩ ↩2

  7. markdown-it/markdown-it-sub README, npm 2.0.0, 2023-12-05. https://github.com/markdown-it/markdown-it-sub ↩

  8. GitHub Flavored Markdown Spec 0.29, §6.11 Disallowed Raw HTML (extension), GitHub Inc., 2019-04-06. https://github.github.com/gfm/ — Wayback: https://web.archive.org/web/20261028012336/https://github.github.com/gfm/ ↩

  9. github/markup README, GitHub Inc., last commit 2026-10-19. https://github.com/github/markup ↩

  10. gjtorikian/html-pipeline v2.14.3 lib/html/pipeline/sanitization_filter.rb, release 2021-07-07. https://github.com/gjtorikian/html-pipeline/blob/v2.14.3/lib/html/pipeline/sanitization_filter.rb — Note: historical open-source predecessor; not GitHub.com's live ruleset. Used as corroborating evidence for the sanitization behaviour measured in own receipts. ↩

  11. WHATWG, HTML Living Standard §4.5.21 The u element, Last Updated 26 October 2026. https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-u-element ↩