How to Underline Text in Markdown
Markdown has no underline syntax. Here is the honest explanation why, the HTML workarounds that actually render, where each one gets stripped, and the Discord gotcha to watch for.
Short answer: There is no markdown underline syntax. Markdown has bold, italic, and strikethrough, and that's the list. To underline text you have to drop into HTML: <ins>underlined</ins> is the option GitHub documents and supports, and <u>underlined</u> works in renderers that pass raw HTML through. On platforms that block HTML entirely — Reddit, Slack, Discord — underlining is not possible at all.
This is the answer nobody wants, so it's worth explaining why the gap exists rather than just listing workarounds. It isn't an oversight.
Why Markdown Has No Underline
Markdown was designed in 2004 as a shorthand for writing HTML, and its formatting vocabulary maps to what HTML considered semantic: emphasis, strong emphasis, code, quotation. Underline was never in that set, for two reasons that still hold up.
Underline means "link" on the web. Every user who has ever used a browser has been trained that underlined text is clickable. Underlining a non-link produces a small, repeated moment of confusion, and it's the reason typographers and interface designers avoid it too. Underline in print existed because typewriters couldn't do italics.
It isn't semantic. Bold and italic describe intent — this is emphasized, this is important. Underline describes an appearance with no consistent meaning behind it. HTML's own <u> element was deprecated in HTML 4 for exactly this reason, then reinstated in HTML5 with a much narrower definition: text with a non-textual annotation, like a spell-check squiggle or a proper name in Chinese.
Markdown, being a semantic format that happens to look like text, has no natural place for a purely visual instruction.
Option 1: The <ins> Tag (Best for GitHub)
<ins> marks inserted text. Browsers render it underlined by default, and — importantly — it's on GitHub's HTML allowlist. GitHub's own formatting documentation lists it as the way to underline.
This is an <ins>underlined</ins> word.
Renders with "underlined" underlined. This is the most portable option in practice: it works on GitHub, GitLab, most static site generators, and any renderer that permits raw HTML.
The tradeoff is semantic honesty. <ins> means "this content was added," typically paired with <del> for tracked changes. If a screen reader announces it as an insertion, that's technically correct behavior and possibly not what you meant.
Option 2: The <u> Tag
This is an <u>underlined</u> word.
<u> is the direct, obvious choice and it renders correctly in browsers, Obsidian, VS Code's preview, and most markdown editors.
It does not work on GitHub. GitHub sanitizes rendered markdown against an allowlist of HTML elements, and u isn't on it. The tag is stripped and you're left with plain, unformatted text. No warning, no error — the styling just doesn't appear. This surprises people constantly, because the same file previews correctly in their local editor.
Use <u> for your own site, notes, or documentation pipeline. Use <ins> when GitHub is in the picture.
Option 3: A CSS Style Attribute
If you control the rendering, inline CSS gives you exact control:
<span style="text-decoration: underline">underlined</span>
You can also pick a different treatment entirely — text-decoration: underline dotted, or a colored underline — which is often what people actually want when they reach for underline.
GitHub strips style attributes. The sanitizer allows a set of attributes that includes align, width, and title, but not style. The span survives; the underline doesn't. Same story on most hosted platforms that render user-supplied markdown.
This option is for your own site or an internal docs system where you own the pipeline. In that case, a CSS class in your stylesheet is cleaner than an inline style anyway.
Option 4: Pandoc's Bracketed Span
If your markdown goes through Pandoc on its way to DOCX, PDF, or LaTeX, raw HTML tags are usually dropped — HTML means nothing to a Word document. Pandoc's native syntax for this is a bracketed span with the underline class:
[This text is underlined]{.underline}
Pandoc converts that to real underlining in DOCX, LaTeX, and HTML output. It renders as literal brackets everywhere else, so it's a build-pipeline tool rather than something to put in a README.
The Discord Gotcha
Discord uses its own markdown-like syntax, and it made a decision that breaks portability in a genuinely confusing way:
__This text is underlined in Discord__
In Discord, double underscores mean underline. In standard markdown — CommonMark, GFM, and effectively everywhere else — double underscores mean bold, identical to **.
So the same characters produce different formatting depending on where you paste them:
| Syntax | Standard markdown | Discord |
|---|---|---|
*text* | Italic | Italic |
_text_ | Italic | Italic |
**text** | Bold | Bold |
__text__ | Bold | Underline |
***text*** | Bold italic | Bold italic |
~~text~~ | Strikethrough (GFM) | Strikethrough |
<u>text</u> | Underline where HTML is allowed | Literal text |
If you draft in a markdown editor and paste into Discord, your bold becomes underline. Draft in Discord and paste into a .md file, and your underline becomes bold. Use ** for bold in anything that might travel between the two — it means the same thing in both.
Why _text_ Gives You Italic
A related and very common assumption: that a single underscore underlines, because the character is literally called an underscore and looks like an underline.
It doesn't. _text_ is italic, exactly equivalent to *text*. Markdown supports both delimiters for the same two levels of emphasis, and which one you use is purely a style preference. There is no third level that maps to underline.
One practical note: underscores don't work inside words in most flavors. snake_case_name stays intact rather than italicizing the middle, which is deliberate — it keeps code identifiers readable in prose. Asterisks do apply mid-word, so un*frigging*believable italicizes.
Where HTML Underlining Gets Stripped
Raw HTML in markdown is a per-platform privilege, not a guarantee:
| Platform | <u> | <ins> | style attribute |
|---|---|---|---|
| GitHub | Stripped | Works | Stripped |
| GitLab | Works | Works | Stripped |
| Obsidian | Works | Works | Works |
| VS Code preview | Works | Works | Works |
| Not allowed | Not allowed | Not allowed | |
| Discord | Not allowed | Not allowed | Not allowed |
| Slack | Not allowed | Not allowed | Not allowed |
| Static site generators | Depends on the raw-HTML setting | Same | Same |
| MDX | Parsed as JSX — usually works | Same | Needs an object, not a string |
Two additional cases worth flagging. Many static site generators disable raw HTML in markdown by default as a security measure — in remark, that's the allowDangerousHtml option — and will escape your tags into visible text. And in MDX, HTML is JSX, so style takes a JavaScript object rather than a CSS string, and unclosed tags are a build error rather than a rendering quirk. More on those differences in markdown extensions: GFM and MDX.
What to Use Instead
Before working around the gap, it's worth asking what the underline is doing:
- Emphasizing a word in a sentence? Use italic. That's what it's for.
- Marking something critical? Use bold. It scans better than underline in body text.
- Labeling a section? Use a heading. Underlining a line of text to make it look like a heading produces a document with no real structure — no table of contents, no anchor links, nothing for a screen reader or an outline view to work with.
- Showing an edit or a correction?
<ins>and~~strikethrough~~are the semantically correct pair, and strikethrough is native GFM. - Following a house style that mandates underline? Then
<ins>, and confirm it survives wherever the document is published.
Bold and italic are native, universal, and impossible to strip. Underline is none of those things.
Seeing What Renders Before You Publish
The frustrating part of all of this is that HTML-in-markdown fails silently. Your tag either renders or quietly disappears, and you find out after publishing.
OpenMark renders raw HTML inline in Document view, so <ins> and <u> show up as they'd appear in a permissive renderer, with the raw source one keystroke away in Markdown view. That tells you the syntax is right — it can't tell you whether GitHub will keep it, which is why the tables above matter. For a broader tour of what's native and what isn't, see how to write markdown and the markdown cheat sheet.
Related gaps in the format worth knowing about: how to center text in markdown and markdown line breaks — both have the same "no native syntax, here's the HTML" shape.
Download OpenMark → — $9.99, one-time, native macOS. Rendered preview and raw source in one window, so you can see exactly what your HTML fallbacks do.