Character-specific guide
A tab is not a number of spaces
The horizontal tab is a single control character, U+0009, decimal 9 in ASCII. It carries no width of its own. What it says is “move to the next tab stop,” and the program displaying the text decides where those stops are. That is the whole reason a file can look perfectly aligned in the editor that produced it and ragged everywhere else.
It also explains the most common surprise: expanding one tab may produce one space, and expanding the very next tab may produce four. The number depends on the column the tab starts in, not on the tab itself.
The expansion math, and the code that runs it
Expansion is deterministic. For a tab that begins at column c with a stop width of n, the number of spaces inserted is n − (c mod n), which is always between 1 and n. The column counter resets at every stored line ending, so CRLF, LF and CR files all behave the same way. This is the function this page runs in your browser:
let column = 0;
let output = '';
for (const character of text) {
if (character === '\t') {
const spacesNeeded = size - (column % size);
output += ' '.repeat(spacesNeeded);
column += spacesNeeded;
} else {
output += character;
column = (character === '\n' || character === '\r') ? 0 : column + 1;
}
}
Note what this is not: it is not text.replace(/\t/g, ' '). Blind replacement inserts four spaces regardless of position, which shifts every column after the first tab and is the usual cause of “I converted tabs to spaces and the alignment got worse.”
Which mode to choose
| Mode | What happens to [TAB][TAB] |
Choose it when |
|---|---|---|
| One space | The whole consecutive run becomes a single regular space. | You want readable prose and no longer need the columns. |
| Remove | Both tab characters are deleted, nothing is inserted. | A tab landed inside a value that should have no separator at all. |
| Expand to tab stops | Each tab advances to the next 2, 4 or 8-column boundary. | The alignment matters and the destination cannot be trusted to honour tabs. |
Where your tabs probably came from
The right mode depends far less on the text than on where it was copied from, because the source determines what the tab was doing.
| Copied from | What the tab means there | Sensible starting point |
|---|---|---|
| Excel, Google Sheets, Numbers | A field separator between cells. This is why a copied range pastes as TSV. | Keep the tabs, or expand them. One space destroys the column boundary. |
| A code editor | Structural indentation at the project's configured width. | Expand at the project's width, or leave it to the project's formatter. |
| Terminal output and log files | Column alignment, usually assuming stops every 8 columns. | Expand at width 8 to keep the columns readable in plain text. |
| A PDF or a slide deck | Usually incidental, produced by the text extractor rather than the author. | One space, then read the result back for run-together words. |
| Word or Google Docs | Often a ruler tab stop, which is paragraph formatting, not a stored character. | No text tool can help. Change the ruler or the paragraph style instead. |
Four places where a tab is not a matter of taste
The tabs-versus-spaces argument is a style debate in most languages. In these four cases it is not, and converting the character changes whether the file works at all.
- GNU Make. Every recipe line must begin with a literal tab character. Replacing it with spaces produces the classic
missing separatorerror, unless the makefile has set.RECIPEPREFIXto something else. - YAML. The specification does not permit tab characters for indentation. A tab that sneaks into a block is a parse error, not a formatting quirk.
- Python 3. Mixing tabs and spaces inconsistently within a block raises
TabError. PEP 8 asks for four spaces, so the fix is usually to convert the whole file rather than one line. - TSV data. The tab is the field delimiter. Changing it is not cleanup, it is data loss, and a row with a missing delimiter will not fail loudly.
Go sits just outside this list: gofmt indents with tabs by convention, so converting them is not an error but will be undone the next time the formatter runs.
Before you replace anything
- Keep the original text until you have checked the result in its real destination.
- Compare the two panes in the marked-up view above rather than the two textareas. That is where a lost column becomes visible.
- Expansion counts Unicode code points, so CJK characters and emoji can still shift the visual alignment even when the column arithmetic is correct.
Two worked examples
A spreadsheet range that has to stay a table
Copying four cells gives you three tabs per row. Load the “Spreadsheet paste” sample above and switch between the modes. One space produces prose and silently destroys the column structure. Expansion at width 8 keeps the columns visually aligned as plain text, which is what you want for an email or a README, though the result is no longer machine-readable TSV.
Tab-indented code going somewhere that renders tabs badly
Load the “Tab-indented code” sample and choose expansion at width 4. Nested lines receive four and eight spaces respectively, matching what the editor was showing. This is the safe direction to convert in: going the other way, from spaces back to tabs, cannot be done reliably by character replacement because the original intent is gone.
Frequently asked questions
How do I convert tabs to 4 spaces?
Choose Expand to Tab Stops and set the width to 4. Be aware that this inserts between one and four spaces per tab depending on the column. If you genuinely need every tab replaced by exactly four spaces regardless of position, that is a different operation and this tool does not perform it, because it breaks alignment more often than it fixes it.
Why did my columns collapse when I pasted into another app?
Most likely the destination renders tabs at a different width, or collapses them entirely. Web pages are a common case: under the usual CSS rules a tab in ordinary markup is collapsible whitespace and renders as a single space. Expanding to spaces before pasting makes the layout independent of the destination.
Does this delete my ordinary spaces?
No. Only U+0009 is targeted. Regular spaces, non-breaking spaces, punctuation and line endings are untouched. If you also need to collapse runs of ordinary and Unicode spaces, use the main space remover afterwards.
Are line breaks preserved?
Yes, in every mode. CRLF, LF and CR sequences are kept exactly as stored. Only horizontal tab characters change.
How do I remove tabs in Notepad or VS Code instead?
In VS Code the built-in command is Convert Indentation to Spaces, which respects the file's configured tab width. In Notepad, find-and-replace cannot type a tab directly, so people usually copy one tab character into the search field first. This page exists mainly because doing it in the browser lets you see the result before committing to it.
Does my text leave the browser?
No. The transformation runs locally in JavaScript. The contents of the input and output boxes are not uploaded for processing.
This page describes behaviour that is pinned by automated tests. The tab replacement and tab-stop expansion functions have unit tests asserting their output at the character-code level, including mixed Windows, Unix and classic Mac line endings. More about how the site is built and tested is on the about page.
Related cleanup options
To strip whitespace from the start or end of each line without touching what is inside it, use Trim Lines. To join wrapped lines while keeping paragraph breaks, use the line break tool. To normalise several kinds of horizontal Unicode whitespace at once, use Remove Spaces Online. For the full list of whitespace code points and what each one does, see the whitespace character reference.