Skip to content

MD033 - No HTML tags

Aliases: no-inline-html

What this rule does

Prevents the use of HTML tags in Markdown - use Markdown syntax instead.

Why this matters

  • Portability: Pure Markdown works everywhere, HTML might be blocked or stripped
  • Security: Many platforms sanitize HTML for security reasons
  • Simplicity: Markdown syntax is cleaner and easier to read than HTML
  • Consistency: Mixing HTML and Markdown creates inconsistent documents

Examples

✅ Correct

# Heading

This is a paragraph with **bold** and *italic* text.

> This is a quote

- List item 1
- List item 2

[Link text](https://example.com)

![Image description](image.png)

Contact us at <[email protected]>
Visit <https://example.com>

❌ Incorrect

# Heading

This is a paragraph with <strong>bold</strong> and <em>italic</em> text.

<blockquote>This is a quote</blockquote>

<ul>
  <li>List item 1</li>
  <li>List item 2</li>
</ul>

<a href="https://example.com">Link text</a>

<img src="image.png" alt="Image description">

🔧 Fixed

# Heading

This is a paragraph with **bold** and *italic* text.

> This is a quote

- List item 1
- List item 2

[Link text](https://example.com)

![Image description](image.png)

Configuration

[MD033]
allowed-elements = []     # List of allowed HTML tags (default: none)
allowed-inside = []       # Tags whose contents are allowed, whatever appears inside (default: none)
disallowed-elements = []  # List of disallowed HTML tags (enables disallowed-only mode)
fix = false               # Enable auto-fix to convert simple HTML to Markdown (default: false)
fix-mode = "conservative" # conservative (default) or relaxed
drop-attributes = ["target", "rel", "width", "height", "align", "class", "id", "style"]  # Used in relaxed mode
strip-wrapper-elements = ["p"]  # Used in relaxed mode
br-style = "trailing-spaces"  # Style for <br> conversion: "trailing-spaces" or "backslash"
# table-allowed-elements = ["br"]  # Optional override for tags inside GFM table cells (see below)

Shorthand aliases are also supported:

[MD033]
allowed = []              # Alias for allowed-elements
disallowed = []           # Alias for disallowed-elements

Example allowing specific tags

[MD033]
allowed-elements = ["br", "hr", "details", "summary"]

This would allow line breaks, horizontal rules, and collapsible sections while blocking other HTML.

Allowing everything Markdown cannot express

Listing tags one by one means revisiting the config every time a document needs another element. The special value no-markdown-equivalent in allowed-elements permits every element that Markdown has no syntax of its own for, so only HTML a writer could have written as Markdown is reported:

[MD033]
allowed-elements = ["no-markdown-equivalent"]

With that setting, <kbd>, <abbr>, <details>, <summary>, <sub>, <sup>, <mark>, <dl> and the like are permitted, while <b>, <em>, <a>, <img>, <ul>, <table> and the other elements with Markdown syntax keep being reported.

Three details are worth knowing:

  • The tags GitHub filters out of rendered Markdown (<script>, <iframe>, <style> and the rest of the GFM list) are never permitted by this value. No reader displays them, so writing them is not about Markdown's expressiveness.
  • What counts as expressible follows the flavor. Pandoc and Quarto write ~sub~, ^sup^ and definition lists, so <sub>, <sup>, <dl>, <dt> and <dd> are reported under those flavors. Obsidian writes ==highlight==, so <mark> is reported there.
  • A hard break inside a GFM table cell has no Markdown equivalent, because the two trailing spaces that express one elsewhere do not survive in a cell. <br> is therefore permitted inside table cells and still reported outside them.

The value can be combined with element names, and it is accepted in table-allowed-elements too:

[MD033]
allowed-elements = ["no-markdown-equivalent", "img"]

Allowing HTML inside a container element

Some documents need one region of raw HTML rather than one kind of tag: a <details> block, a <figure>, a table of contents built by hand. Naming every element that may appear in that region turns the allowlist into a list of tags that are then permitted across the whole document, including where they are not wanted.

allowed-inside names container elements instead. Everything from the opening tag to its matching closing tag is left alone, the container's own tags included:

[MD033]
allowed-inside = ["details"]
<details>
<summary>Show the details</summary>
<a href="./other.md">A link</a>, <b>bold</b>, whatever the block needs.
</details>

This <b>outside</b> is still reported.

Notes:

  • Nesting works as a reader reads it: a closing tag ends the innermost open element of that name. A container left unclosed reaches the end of the document.
  • An element that cannot hold content (<br>, <img> and the other void elements) holds nothing, so naming one here permits nothing. Name it in allowed-elements.
  • A container written inside a code block or code span is quoted text, not an open container, so it opens no region.
  • When table-allowed-elements is set, it stays the whole answer inside a GFM table cell and allowed-inside does not apply there. With it unset, a container that spans a cell covers the cell.
  • disallowed-elements names what is wrong wherever it appears, so allowed-inside takes no part in disallowed-only mode.

GFM Security Mode (disallowed-only)

For GitHub Flavored Markdown, you can use the disallowed-elements option to only flag security-sensitive HTML tags while allowing all other HTML. Use the special value "gfm" to automatically include all GFM-disallowed tags:

[MD033]
disallowed-elements = ["gfm"]

This flags only these security-sensitive tags:

  • <title>, <textarea>, <style>, <xmp>, <iframe>
  • <noembed>, <noframes>, <script>, <plaintext>

These are the same tags that GitHub filters from rendered markdown for security reasons.

Custom disallowed tags

You can also specify your own list of disallowed tags:

[MD033]
disallowed-elements = ["script", "iframe", "style"]

Or combine GFM tags with custom ones:

[MD033]
disallowed-elements = ["gfm", "marquee", "blink"]

mdbook projects with semantic HTML

mdbook documentation often uses HTML with CSS classes to add semantic meaning that pure Markdown cannot express (e.g., marking text as filenames, captions, or warnings). For mdbook projects, you can allow semantic containers:

[tool.rumdl.MD033]
allowed-elements = ["div", "span"]

This permits semantic HTML like:

  • <span class="filename">src/main.rs</span> - Filename styling
  • <div class="warning">Important note</div> - Warning boxes
  • <span class="caption">Figure 1: Architecture</span> - Figure captions

While still catching potentially problematic HTML like <em>, <strong>, or <script> tags that have Markdown equivalents or security concerns.

Allowing different tags inside GFM tables

GFM tables cannot contain block-level Markdown, so authors often reach for a small amount of inline HTML (typically <br>) to express line breaks within a cell. Use table-allowed-elements to grant a different allowlist inside table cells than the document-wide allowed-elements. The legacy snake_case keys table_allowed_elements and table_allowed are also accepted.

The option distinguishes three states:

Value Behavior
unset (default) In-table tags fall back to allowed-elements.
[] (explicit empty list) No tags are permitted inside table cells, even ones in allowed.
["tag", ...] (explicit list) Only the listed tags are permitted in table cells.

Tags outside GFM tables are never affected by this option.

[MD033]
allowed-elements = ["sub", "sup"]      # whole-document allowlist
table-allowed-elements = ["br", "img"] # additionally permit only these in tables

The no-markdown-equivalent value works here too, and inside a cell it also permits <br>, which has no Markdown equivalent there:

[MD033]
table-allowed-elements = ["no-markdown-equivalent"]

This mirrors markdownlint's table_allowed_elements.

Automatic fixes

Auto-fix for MD033 is opt-in (disabled by default). Enable it with:

[MD033]
fix = true

When enabled, simple HTML tags are converted to their Markdown equivalents:

HTML Tag Markdown Equivalent
<em>text</em>, <i>text</i> *text*
<strong>text</strong>, <b>text</b> **text**
<code>text</code> `text`
<a href="../url">text</a> [text](url)
<a href="../url" title="tip">text</a> [text](url "tip")
<img src="../url" alt="text"> ![text](url)
<img src="../url" alt="text" title="tip"> ![text](url "tip")
<br>, <br/> Two trailing spaces + newline
<hr>, <hr/> ---

Relaxed conversion mode

By default, MD033 uses conservative conversion. In conservative mode, links and images with significant extra attributes are not converted.

If you want broader conversion for common real-world docs HTML, enable relaxed mode:

[MD033]
fix = true
fix-mode = "relaxed"

In relaxed mode:

  • <a href="../..." target="_blank">...</a> can be converted to Markdown link syntax
  • <img src="../..." alt="..." width="120"> can be converted to Markdown image syntax
  • configured wrapper elements (default: p) can be stripped once their inner content no longer contains HTML tags

You can customize which attributes are dropped and which wrappers are stripped:

[MD033]
fix = true
fix-mode = "relaxed"
drop-attributes = ["target", "rel", "width", "height", "align", "class", "id", "style"]
strip-wrapper-elements = ["p"]

Link and image conversion safety:

Links (<a>) and images (<img>) are only converted when safe:

  • URL must use a safe scheme (http, https, mailto, tel, ftp) or be a relative path
  • Dangerous schemes like javascript:, data:, vbscript: are never converted
  • URL encoding bypass attempts are detected and blocked (java%73cript:, javascript&#58;)
  • Conservative mode: tags with significant extra attributes are not converted
  • Relaxed mode: only attributes in drop-attributes are dropped; unknown extra attributes still block conversion
  • Event handler attributes (onclick, onload, etc.) are never dropped, even in relaxed mode
  • The title attribute IS supported and preserved in the Markdown output
  • Links with nested HTML content are not converted
  • Special characters in URLs (parentheses) and text (brackets) are properly escaped

Limitations:

  • Conservative mode: tags with attributes beyond href/src/alt/title are usually not converted
  • Relaxed mode: wrapper stripping is applied only after wrapper inner content no longer contains HTML tags
  • Tags with nested HTML content are not converted to Markdown
  • Complex tags (like <div>, <span>) have their content extracted but are not converted to Markdown equivalents
  • For deeply nested HTML, you may need to run the fix multiple times

Line break style:

By default, <br> tags are converted to two trailing spaces followed by a newline (CommonMark standard). You can use backslash-style line breaks instead:

[MD033]
fix = true
br-style = "backslash"  # Converts <br> to backslash + newline

What's allowed

These are not considered HTML and are allowed:

  • HTML comments: <!-- This is a comment -->
  • Email autolinks: <[email protected]>
  • URL autolinks: <https://example.com>
  • FTP autolinks: <ftp://files.example.com>

Learn more

  • MD046 - Code block style should be consistent
  • MD034 - URLs should be formatted as links