Pasting minified HTML into a formatter feels like a free operation. It is not. HTML is one of the few languages where whitespace is content, and a beautifier that indents everything the way a JSON formatter would will quietly change what your page looks like. This guide explains exactly where the line is - which whitespace a browser throws away and which it paints on the screen - so you can indent messy markup and know that nothing moved. The HTML Beautifier applies these rules for you.
Why formatting HTML is not free
Browsers process whitespace with a rule usually called collapsing. Inside a run of text, any sequence of spaces, tabs and newlines is treated as one single space. That is why you can wrap a paragraph across ten source lines and it still reads as one flowing sentence.
The catch is the word "one". A newline does not disappear - it collapses to a space. So if two tags were touching and a formatter puts them on separate lines, the browser now sees a space between them that was not there before.
Here is the whole problem in one line of markup:
<div class="tags"><span>new</span><span>sale</span></div>
That renders as newsale - two badges pressed together, which is almost certainly what the CSS expects. Now let a naive formatter indent it:
<div class="tags">, newline, two spaces, <span>new</span>, newline, two spaces, <span>sale</span>, newline, </div>
The newline plus indent between the two spans collapses to a single space, and the page now reads new sale. Nothing warned you. The HTML is still valid, the diff looks like pure whitespace, and the layout shifted by exactly one space character - enough to break a pill row, a set of inline-block cards, or an icon sitting flush against its label.
The rule that makes it safe: block versus inline
The fix is not to avoid line breaks. It is to only put them where the browser is guaranteed to discard them, and CSS is precise about where that is. Whitespace is removed when it sits at the start of a line box, at the end of one, or next to a block-level box.
In practice that gives two families of element:
- Block-level: div, p, section, article, header, footer, nav, main, aside, ul, ol, li, dl, dt, dd, table, tr, td, th, form, fieldset, h1 through h6, blockquote, figure, pre and hr. Whitespace touching these is never rendered, so you can indent them as deeply as you like.
- Inline: span, a, b, strong, i, em, code, small, label, button, input, img, select, svg, br and anything else that flows along with text. Whitespace beside these is a real space on the page, so a line break between two of them changes the output.
So the safe behaviour is: indent block-level elements one per line, and keep anything that holds text or inline tags on a single line with its text untouched. A paragraph such as <p>Some <b>bold</b> text.</p> stays exactly as written, because breaking it apart would insert line breaks into the sentence itself.
This is the biggest difference between formatters. Many will split a run of inline tags to keep lines short, and on most pages you never notice - until the one time you do.
A worked example
Take a fragment of real, minified markup:
<div class="card"><h2>Title</h2><ul><li>One<li>Two</ul><p>Some <b>bold</b> text.</p></div>
Run it through the HTML Beautifier and the div, h2, ul, the two li items and the p each get their own line, indented by depth. The contents of the h2 and the p are left alone, and the bold tag stays inside the sentence.
Notice the two bare li tags. That is legal HTML - the closing tag on a list item is optional, and a browser closes one li when the next one opens. A formatter needs that rule to indent the second item at the right depth, and it must not "helpfully" add the missing tags: in a framework template, an added closing tag changes what gets parsed.
A quick way to check any formatter: strip all whitespace from what you pasted in and from what came out. If the two strings are not identical, the tool rewrote your markup rather than formatting it.
The parts that must never be touched
Four elements carry content that indentation would corrupt outright:
- pre - every space inside is on the page. There is a second trap here: a browser silently swallows a newline immediately after the opening pre tag, so a formatter that adds one has removed a blank line from your output.
- textarea - the same, except the whitespace ends up in the field's value and gets submitted with the form.
- script - re-indenting can edit a string. A line inside a template literal, or a string continued with a trailing backslash, has its leading spaces as part of the value, so shifting the line changes the data.
- style - CSS is safe to shift in almost all cases, but a value continued across lines with a backslash has the same problem.
Shifting a script or style block to a new indentation level looks far better than leaving it at column zero. The honest approach is to shift it only when the block provably holds no multi-line string, and to say so when it does not.
One more case is visible from the markup itself: an inline style="white-space: pre" turns preservation back on for any element, and a formatter can honour that. A utility class doing the same thing cannot be seen from the HTML at all - worth remembering if you use one.
Custom elements and framework templates
An element the browser does not recognise gets display: inline by default. That includes every custom element and web component, so <my-badge>a</my-badge><my-badge>b</my-badge> behaves exactly like the two spans from earlier - splitting them adds a space.
If your components are styled as blocks, that default is too cautious, which is why the beautifier has a switch to treat custom elements as block-level. It is off by default because it is the one option that can change how a page renders, and it should be a decision you make rather than one made for you.
Templates bring a second hazard: case. HTML tag names are case-insensitive, so plenty of formatters lower-case them as a tidying step. In a Vue single-file component or any JSX-style template, <MyComponent> and <mycomponent> are different things, and lower-casing breaks the build. Attribute quoting is the same story - rewriting single quotes to double can break a value holding a double quote of its own.
Beautify or minify?
The two serve different moments. Beautify when reading or debugging: markup from a network response, a template someone else wrote, or build output you need to audit. Minify when shipping, to cut bytes on the wire - the HTML Minifier does the reverse trip.
Because both only ever touch whitespace that is not rendered, they compose: minify a document, beautify it again months later, and you land back on the same structure with the same text. If a round trip does not come back clean, one of the two is editing your markup rather than formatting it.
Frequently asked questions
- Does beautifying HTML change how the page looks?
- It should not, but many formatters do change it. The risk is entirely about inline elements: in HTML a newline collapses to a single space rather than to nothing, so putting two touching inline tags such as <span>a</span><span>b</span> on separate lines adds a visible gap. Whitespace next to a block-level element - a div, p, li, tr and so on - is discarded by the browser, so indenting those is free. A safe formatter breaks lines only at block-level boundaries and keeps anything holding text on one line.
- Why is my whole paragraph on a single long line after formatting?
- Because breaking it would change it. A paragraph that mixes text with inline tags, like <p>Some <b>bold</b> text.</p>, cannot have its children pushed onto separate lines without inserting line breaks into the sentence, and those line breaks become spaces on the page. The same applies to a row of links in a nav or a set of badges in a div. The trade is deliberate: a long line in your editor, in exchange for a page that renders exactly as it did before.
- Can a formatter add the closing tags my HTML is missing?
- It can, and it should not do it silently. A bare li, p, tr, td or option is valid HTML - the specification makes those closing tags optional, and browsers close them by rule when the next sibling or the parent's end tag appears. A formatter needs those rules to indent correctly, but inserting the tags is an edit rather than a formatting change, and in a framework template it alters what gets parsed. The better behaviour is to indent them correctly, tell you how many were found, and leave your markup exactly as it was.