Developers & Technical · Guide · Developer Utilities
How to format HTML properly
HTML formatting rules, void elements, boolean attributes, whitespace-sensitive tags, template vs output HTML, Prettier and HTMLHint tooling.
HTML formatting sits at the intersection of developer ergonomics and runtime reality. Well-formatted HTML is easier to read, easier to diff in code review, and easier to debug in DevTools — but strict formatting rules also matter for SSR hydration, SEO parsers, and the brittle middle ground where whitespace becomes visible space. This guide covers HTML syntax basics, what formatters do and don’t fix, the specific rules that matter (void elements, boolean attributes, whitespace-sensitive tags), tooling (Prettier, HTMLHint, hand-rolled), and the subtle differences between formatting template HTML versus output HTML.
Advertisement
What “formatting” actually covers
HTML formatting is the style layer: indentation, attribute wrapping, quote consistency, blank lines, closing slashes. It’s separate from:
Validation — is the HTML actually valid per spec? Formatters mostly don’t check this.
Linting — does it follow best practices? Use HTMLHint, eslint-plugin-html.
Accessibility — does it have proper semantics? Use axe-core or eslint-plugin-jsx-a11y.
Formatting is cosmetic. The browser ignores most of it. But developers read HTML thousands of times and messy HTML slows down every edit.
Indentation rules
Standard: 2 spaces per nesting level. Prettier defaults to 2, most style guides agree. 4 spaces and tabs both work; pick one and enforce it.
Nest children: each child element gets one more indent. Attributes on long lines wrap one per line, aligned to the first attribute or indented from the tag.
Closing tags: match the opening tag’s indent. Never “dangle” closing tags weirdly mid-line unless they’re inline elements inside text.
Inline vs block: <strong>, <em>, <a>, <code> stay on the same line as their text. Putting them on their own line introduces whitespace that changes rendering.
Void elements — no closing tag needed
These HTML5 elements never have content: <area>, <base>, <br>, <col>, <embed>, <hr>, <img>, <input>, <link>, <meta>, <source>, <track>, <wbr>.
HTML5 allows <br> or <br/>. XHTML required the self-close slash. Pick one style, enforce it.
Prettier removes the slash by default for HTML; keeps it for JSX (because JSX requires it).
Attribute style
Quotes: double quotes are standard. Single quotes parse but look foreign. Prettier enforces double by default.
Boolean attributes: <input required> is equivalent to <input required=""> or <input required="required">. Short form is preferred.
Attribute wrapping: once an element has 3+ attributes or one is long, wrap to one-per-line. Keeps diffs surgical when you change a single attribute.
Order: loose convention is class, id, data-*, then everything else. Prettier doesn’t enforce order.
Whitespace-sensitive tags
Most HTML ignores whitespace, but these tags don’t:
<pre>: preserves all whitespace. Code blocks, ASCII art, poetry. Formatters must not reformat inside <pre>.
<textarea>: preserves whitespace in its initial content. Common gotcha — indenting HTML around a <textarea> puts that indentation in the user’s input.
<code> inside <pre>: the <pre><code> pattern for code blocks — the <pre> preserves whitespace, the <code> gives semantic meaning. Must be on the same line as their content.
Inline elements: <span>, <a>, etc. between text — the space between them is rendered. Breaking to a new line with indentation introduces extra whitespace you can see.
Template HTML vs output HTML
Template engines (EJS, Jinja, Blade, Svelte, JSX) let you format for readability, then output compressed HTML. Two different optimization targets:
Source (readable): indented, commented, separated into sections. Formatters apply here.
Output (byte-efficient): minified, no whitespace, no comments. Build tools handle this.
Never hand-minify source HTML. Modern bundlers (Vite, Webpack, Next.js) minify output automatically in production.
Semantic ordering
Beyond formatting, HTML has a structural order that matters:
Heading hierarchy: one <h1> per page; don’t skip levels (h2 → h4 without h3). Affects accessibility and SEO.
Document structure: <header> → <main> → <footer> with <nav>, <article>, and <section> inside. Avoid divs-all-the-way where a semantic tag exists.
Head element order: <meta charset> before anything else, viewport next, then title, then CSS, then scripts. Order matters for parsing performance.
Prettier, HTMLHint, and friends
Prettier: the modern default formatter. Opinionated, minimal config. Integrates with editor save hooks. Handles HTML, CSS, JS, JSX, Vue, Svelte. Can’t fix everything (wrapping rules and comment placement have quirks).
HTMLHint: linter, not formatter. Catches unclosed tags, duplicate IDs, missing alt text, invalid attributes. Run alongside Prettier, not instead.
js-beautify: older formatter, still used. More configuration, less opinionated than Prettier.
Tidy (HTML Tidy): decades-old C utility. Cleans up broken HTML, useful for fixing legacy CMS output. Rarely used in modern codebases.
CSS inside HTML
<style> blocks should be formatted as standalone CSS (same indentation, selectors on own lines, one declaration per line). Prettier handles this.
Inline styles (style="...") break around at ~80 chars. Complex inline styles are a code smell — consider extracting to a class.
Common mistakes
Hand-formatting HTML in 2026. Use a formatter. Every minute you spend aligning attributes is wasted.
Formatting HTML inside strings. Concatenating HTML strings in JavaScript disables all formatting and opens XSS holes. Use a templating system or JSX.
Over-indenting. Every level of nesting costs horizontal screen. Deep nesting is usually a sign the markup can flatten.
Formatting <pre> content. Formatter reformats code inside <pre>, breaking it. Most formatters respect <pre> and <textarea> but check your config.
Treating HTML like XML. HTML5 is not XML. It’s more lenient (missing closing tags often okay) and less lenient (some elements can’t self-close). Use an HTML-aware formatter, not a generic XML one.
Ignoring encoding. Save as UTF-8 without BOM. Declare charset first thing in <head>.
Run the numbers
Format HTML instantly with the HTML formatter. Pair with the HTML to markdown converter when extracting content from pages, and the XML formatter when the document turns out to be XHTML or XML-shaped.
Advertisement