Skip to content

HTML Formatter

Indent and format HTML markup for readability.

An HTML formatter takes an HTML string and re-emits it with consistent indentation and line breaks. The tool above runs the formatter in your browser; nothing leaves your machine. The interesting question is what the formatter has to be careful not to change.

What gets indented

The formatter’s main job: nest each element under its parent and indent appropriately.

Input:  <article><h1>Title</h1><p>Body text</p></article>

Output:
<article>
  <h1>Title</h1>
  <p>Body text</p>
</article>

Default indent is 2 spaces (sometimes 4, sometimes tabs). Most formatters offer the choice.

What’s whitespace-sensitive

HTML has elements where whitespace matters and where it doesn’t:

ElementWhitespace handling
Block elements (<div>, <p>, <article>, <section>)Whitespace between children is collapsed and rendered minimally
Inline elements (<span>, <a>, <em>, <strong>)Whitespace between/around is rendered as spaces
<pre>Whitespace is preserved exactly
<textarea>Whitespace is preserved exactly
<code> (inside <pre>)Whitespace is preserved
<script>, <style>Bytes preserved; not parsed as HTML

A naïve formatter that adds line breaks anywhere can change behavior:

Input:  <p>Hello <a href="...">world</a>!</p>

If the formatter naively breaks lines:

<p>
  Hello
  <a href="...">world</a>!
</p>

The rendered text now has extra spaces around “world” — a subtle difference that affects spacing. A correct HTML formatter knows that <a> is inline and either:

  • Keeps the inline element on the same line as the surrounding text.
  • Breaks but uses careful whitespace that preserves rendering.

The tool above keeps inline elements on the same line by default.

Void elements

HTML has elements that don’t have closing tags: <img>, <br>, <hr>, <meta>, <link>, <input>. These are void elements — self-closing by definition.

<img src="...">           ← HTML5
<img src="..." />         ← XHTML / JSX
<br>                      ← HTML5
<br/>                     ← XHTML / JSX

A formatter has to decide which form to emit. Most modern formatters default to HTML5 (no slash). XHTML-strict formatters add the slash.

Attributes

<input
  type="email"
  name="email"
  required
  placeholder="you@example.com"
>

For elements with many attributes, formatters break each attribute onto its own line. The threshold (how many attributes before breaking) varies; common defaults are 3–4 attributes or a line-length limit (80 or 100 characters).

Boolean attributes (required, disabled, checked) don’t need values in HTML5 — required is equivalent to required="required". Formatters typically preserve whichever form the source used.

Comments

<!-- A regular comment -->

<!--[if IE]>
  <p>Internet Explorer-specific markup</p>
<![endif]-->

The first form is just a comment. The second is an “Internet Explorer conditional comment” — IE used to interpret these specially. Modern browsers ignore them; the formatter should preserve their structure exactly because some legacy systems still use them.

Doctype

<!DOCTYPE html>          ← HTML5
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "...">  ← HTML 4

Always at the top, no indentation. Formatters preserve whatever doctype the source had.

What the formatter doesn’t do

  • Doesn’t add missing closing tags. Browsers tolerate sloppy HTML (unclosed <li>, <td>, <p>); a formatter usually preserves the source’s structure rather than “fixing” it.
  • Doesn’t validate. Format-and-emit is a different operation from validate. An HTML linter (HTMLHint, vnu.jar) does validation; the formatter does not.
  • Doesn’t reorder attributes. Source order is preserved.
  • Doesn’t remove or merge classes. <div class="a a"> stays as-is.
  • Doesn’t optimize. A formatter is the inverse of a minifier; it doesn’t strip anything.

When formatting hurts

  • Auto-generated HTML. Build-tool output (React-rendered HTML, framework-rendered SSR output) usually doesn’t need formatting and may break if formatted (Vue’s whitespace handling is opinionated).
  • Email HTML. Many email clients are picky about whitespace inside specific elements. Format with care; test in actual email clients.
  • HTML inside JavaScript template literals. Some formatters leave these alone; some try to format them and break things.

Idempotence

Format a file once; format the result again — the second pass should produce identical output. If the formatter is non-idempotent, it has a bug. Easy to test: pipe the source through twice and diff the results.

Common pitfalls

  • Formatting <pre> content. Whitespace inside <pre> is significant. A formatter that re-indents inside <pre> changes rendered output. Most formatters know to leave <pre> alone.
  • Formatting JSX/Vue/Svelte/Astro syntax. These look like HTML but aren’t strictly. Use a formatter that understands the specific syntax (Prettier with the right plugin) rather than a pure-HTML one.
  • Stripping intentional &nbsp;. Some formatters “normalize” whitespace and turn &nbsp; into spaces, breaking layout. Most don’t, but check after formatting if &nbsp; mattered to the design.
  • Re-adding closing tags. Some formatters insert </li> and </p> even when the spec allows them to be omitted. Mostly harmless; sometimes breaks template logic.
  • Formatting an unclosed tag. A missing </div> confuses the parser; output is unpredictable. Fix the markup first, format second.

For the inverse direction, see HTML minifier. For other format conversions, see JSON formatter, CSS formatter, XML formatter.