Developer ToolsMay 22, 2026

HTML Formatting and Beautification: Clean Markup for Maintainable Code

Unformatted HTML is hard to review, hard to debug, and hard to maintain. This guide covers HTML formatting conventions, when to beautify vs. minify, the tools that automate both, and the patterns that keep markup readable as a project grows.

Olivia Bennett
Olivia Bennett · Full-stack Developer
Full-stack developer focused on developer tooling and web performance. Writes about the formats, patterns, and shortcuts devs reach for every day.
htmlformattingbeautifiercode qualityweb developmentmarkupindentation

HTML that was generated by a tool, copied from Stack Overflow, or modified incrementally by five developers over two years tends to accumulate formatting inconsistencies: inconsistent indentation, mixed quote styles, attributes on multiple lines or all on one, no blank lines between logical sections, or the opposite — excessive blank lines that make the file scroll forever.

None of this breaks the browser. HTML parsers are famously tolerant and render most malformed markup correctly. But humans are not HTML parsers, and poorly formatted markup is genuinely harder to review, debug, and maintain. A PR diff of reformatted HTML is unreadable. An attribute hidden at character 300 of an 800-character line is easy to miss.

This guide covers the conventions that keep HTML readable, the tools that automate them, and when to go in the opposite direction (minification).


HTML Formatting Conventions

Indentation

The universal HTML formatting convention is 2-space indentation, with each child element indented one level deeper than its parent.

<!-- Preferred: 2-space indentation -->
<div class="card">
  <header class="card__header">
    <h2 class="card__title">Card Title</h2>
  </header>
  <div class="card__body">
    <p>Card content goes here.</p>
  </div>
  <footer class="card__footer">
    <button type="button" class="btn btn-primary">Action</button>
  </footer>
</div>

Some teams use 4-space indentation; a small number use tabs (and rely on editor settings to display them at the preferred width). The specific choice matters less than consistency. Mixed indentation — 2 spaces in some files, 4 in others, tabs in others — is the problem.

When not to indent:

Inline elements within text should not force a line break:

<!-- Good — inline elements stay on the same line as text -->
<p>This is text with <strong>bold</strong> and <a href="/link">a link</a> inside.</p>

<!-- Bad — unnecessary line breaks inside inline flow -->
<p>
  This is text with
  <strong>bold</strong>
  and
  <a href="/link">a link</a>
  inside.
</p>

The second example forces unwanted whitespace rendering because HTML collapses whitespace but does not completely eliminate it between inline elements.

Attribute formatting

For elements with few short attributes, keep everything on one line:

<a href="/docs" class="nav-link">Documentation</a>
<input type="text" name="email" placeholder="Your email">

For elements with many attributes or long values, put each attribute on its own line, aligned below the opening tag name:

<!-- Long attributes — one per line, closing tag on its own line -->
<input
  type="email"
  id="email-field"
  name="email"
  class="form-control form-control--lg"
  placeholder="Enter your email address"
  autocomplete="email"
  required
>

The threshold for when to break attributes is subjective — most style guides suggest breaking at 80–100 characters of total line length, or when there are 4+ attributes.

Self-closing tags

HTML5 does not require self-closing slashes for void elements (<input>, <img>, <br>, <hr>, <meta>, <link>). Both are valid:

<!-- Both valid in HTML5 -->
<input type="text">
<input type="text" />
<br>
<br />

The self-closing slash convention is carried over from XHTML. In HTML5 contexts, omitting it is the modern preference. In JSX/React, the self-closing slash is required: <input type="text" />.

Choose one convention and use it consistently. Mixing both in the same file is the only wrong answer.

Quote style

HTML allows both double and single quotes for attribute values, and technically allows no quotes for simple values:

<input type="text">         <!-- no quotes — valid for simple values -->
<input type='text'>         <!-- single quotes — valid -->
<input type="text">         <!-- double quotes — recommended -->

Use double quotes. They are the HTML spec's convention, the most common convention in editors and linters, and they avoid confusion with JavaScript strings (which more often use single or template quotes).

Boolean attributes

Boolean attributes (required, disabled, checked, readonly, multiple, selected, defer, async) do not need a value in HTML5:

<!-- All equivalent — the attribute's presence is what matters -->
<input required>
<input required="">
<input required="required">

<!-- Preferred in HTML5 -->
<input required>

Document Structure

Minimal valid HTML5 document

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Page Title</title>
</head>
<body>
  <main>
    <h1>Page Heading</h1>
  </main>
</body>
</html>

The <!DOCTYPE html> declaration triggers standards mode. Without it, browsers use quirks mode, which changes how certain CSS properties work. Always include it.

<meta charset="UTF-8"> must be in the first 1 024 bytes of the document — before any content that might contain Unicode.

lang="en" on the <html> element is required for accessibility (screen readers use it to select pronunciation rules) and for search engines.

Logical sectioning with blank lines

Use one blank line between major sections within <head> and between major sections within <body>:

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Page Title | Site Name</title>
  <meta name="description" content="Page description for search engines.">

  <link rel="stylesheet" href="/styles/globals.css">
  <link rel="stylesheet" href="/styles/page.css">

  <script src="/scripts/analytics.js" defer></script>
</head>

Blank lines are visual separators — they cost nothing in file size after minification and help readers navigate the document.


When to Beautify HTML

During development and code review

Formatted HTML should be what you commit to version control. Diffs of formatted code are readable; diffs of minified code are useless for review.

If your build pipeline minifies HTML for production, the minification happens at build time — source code stays formatted.

After using an HTML generator

CMS exports, email template builders, WordPress theme files, and HTML-to-code tools often produce unindented or inconsistently indented markup. Run it through an HTML beautifier before incorporating it into your project.

After receiving third-party templates

HTML templates from marketplaces, design agencies, or clients often arrive with non-standard formatting. Normalise it before adding it to your codebase.

After debugging that changed indentation

It is easy to add a wrapper div during debugging and forget to re-indent the children. Or to paste code from a different context with different indentation. A quick beautifier pass restores consistency.

Use the HTML Beautifier to format any HTML — paste in the raw markup and get properly indented, consistently formatted output.


When to Minify HTML

Minified HTML removes whitespace, comments, and optionally shortens attribute values and boolean attributes. The result is smaller file sizes and faster transfer times.

<!-- Before minification: 312 bytes -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Example Page</title>
</head>
<body>
  <main>
    <h1>Hello, World!</h1>
    <p>This is a paragraph with some text content.</p>
  </main>
</body>
</html>

<!-- After minification: 168 bytes — 46% smaller -->
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Example Page</title></head><body><main><h1>Hello, World!</h1><p>This is a paragraph with some text content.</p></main></body></html>

When minification is worth it:

  • Static HTML files served without a CDN or compression
  • Email HTML (email clients are inconsistent about gzip support)
  • Large documents with significant whitespace

When minification has minimal impact:

  • HTML served with gzip/Brotli compression — most whitespace is already compressed. A typical HTML file compresses 70–85%; the whitespace that minification removes is largely already eliminated by compression.
  • HTML generated server-side on each request — the benefit is per-byte smaller response; the cost is CPU on every request.
  • HTML in a Next.js / Nuxt / SvelteKit app — framework build tools handle this.

For most modern web apps served with compression, HTML minification saves single-digit kilobytes per page — measurable but rarely the highest-impact optimisation.

Use the HTML Minifier when you need to reduce HTML file size — for emails, static assets, or any context where compression is not available.


Automated Formatting with Prettier

Prettier is the dominant auto-formatter for HTML (among other languages). Configure it in your project to format HTML automatically on save:

npm install --save-dev prettier

.prettierrc:

{
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "htmlWhitespaceSensitivity": "css",
  "singleAttributePerLine": false
}

.prettierignore:

dist/
build/
*.min.html
vendor/

Run manually:

npx prettier --write "**/*.html"

Or run on commit with lint-staged:

// package.json
{
  "lint-staged": {
    "*.html": ["prettier --write"]
  }
}

htmlWhitespaceSensitivity is the most important HTML-specific Prettier option. Set to "css" to format inline elements according to their CSS display property (inline elements are kept on the same line as surrounding text; block elements are placed on their own line). Set to "ignore" to format purely structurally.


HTML Linting with HTMLHint

Beautification handles formatting; linting catches potential errors:

npm install --save-dev htmlhint

.htmlhintrc:

{
  "tagname-lowercase": true,
  "attr-lowercase": true,
  "attr-value-double-quotes": true,
  "doctype-first": true,
  "tag-pair": true,
  "spec-char-escape": true,
  "id-unique": true,
  "src-not-empty": true,
  "attr-no-duplication": true,
  "title-require": true,
  "alt-require": true,
  "doctype-html5": true,
  "id-class-value": "dash"
}

The alt-require rule catches <img> elements without alt text — an accessibility failure. The id-unique rule catches duplicate IDs — a common source of JavaScript bugs when getElementById returns the wrong element.


Formatting HTML in Different Contexts

Email HTML

Email HTML has different rules: inline styles only (no external CSS), table-based layouts for older clients, no JavaScript. Keep email HTML well-formatted during development and minify for delivery.

HTML in JavaScript template literals

// Keep HTML-in-JS readable with template literals
const cardHTML = `
  <div class="card">
    <h2 class="card__title">${escapeHtml(title)}</h2>
    <p class="card__body">${escapeHtml(body)}</p>
  </div>
`.trim();

The trim removes the leading and trailing newlines from the template literal. Preserve indentation inside the literal for readability.

Generated HTML

If your code generates HTML programmatically, always use a proper HTML builder or template engine — never string concatenation with user data:

// Dangerous — XSS if title contains <script>
const html = '<h1>' + title + '</h1>';

// Safe — use a template engine or the DOM API
const h1 = document.createElement('h1');
h1.textContent = title;  // textContent escapes HTML automatically

The HTML Beautifier handles both well-formed and malformed HTML — useful for cleaning up generated markup that has inconsistent formatting before reviewing it manually.

← All guides