MD033 - No HTML tags
August 5, 2026 · View on GitHub
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)

Contact us at <support@example.com>
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)

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.
Four details are worth knowing:
- The value answers for one tag at a time, so a container it permits does not carry its
contents.
<details>has no Markdown syntax and is permitted, while a<b>or an<a>written inside the block is still reported, exactly as it would be anywhere else. To leave a whole region alone rather than a set of tags, name the container inallowed-inside; the two options combine. - 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.
- Only element names are accepted here. The
no-markdown-equivalentvalue stands for a set of elements rather than one, and permitting the contents of every container Markdown cannot express would leave<div>,<span>,<section>,<figure>and the rest unchecked wherever one is open. Name the containers a document actually uses. - An element that cannot hold content (
<br>,<img>and the other void elements) holds nothing, so naming one here permits nothing. Name it inallowed-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-elementsis set, it stays the whole answer inside a GFM table cell andallowed-insidedoes not apply there. With it unset, a container that spans a cell covers the cell. disallowed-elementsnames what is wrong wherever it appears, soallowed-insidetakes 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"> |  |
<img src="url" alt="text" title="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:) - Conservative mode: tags with significant extra attributes are not converted
- Relaxed mode: only attributes in
drop-attributesare dropped; unknown extra attributes still block conversion - Event handler attributes (
onclick,onload, etc.) are never dropped, even in relaxed mode - The
titleattribute 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:
<user@example.com> - URL autolinks:
<https://example.com> - FTP autolinks:
<ftp://files.example.com> - Tags inside a template shortcode tag:
{{% note <b>x</b> %}}. The text there is a string the template receives, not HTML the document emits. Only the tag is exempt; HTML in the body between a paired opening and closing shortcode is still reported
Learn more
- CommonMark HTML blocks - When HTML is needed
- Markdown Guide - Basic Syntax - Markdown alternatives to HTML