Tidy: JS/CSS/HTML Formatter & BeautifierFormat & beautify JavaScript, TypeScript, JSX/TSX, CSS, SCSS, LESS, HTML & JSON, safely. Tidy respects your VS Code config and never auto-formats or hijacks your files unless you ask. Tidy is a configurable beautifier for JavaScript, TypeScript, JSX/TSX, CSS, SCSS, LESS, HTML and JSON. It does exactly one thing: it reformats a file when you ask it to, via Format Document or Format Selection, and it refuses to write a file back unless the result is provably equivalent to what you started with. It never contributes If you arrived here after a formatter quietly rewrote your code on save, that is the exact behaviour Tidy is built to make impossible. See it in action
These are the real Migrating from JS-CSS-HTML FormatterJS-CSS-HTML Formatter ( Recommended migration (2 minutes), in this order:
Guided migration commandsBoth commands live in the Command Palette (
If lonefy is detected when Tidy activates, you may also see a single, non-modal notification offering to migrate. It is one-shot and deduplicated: it appears at most once per machine/profile, has a “Don’t ask again” action, and is never repeated. Acting without consent is the second most common complaint in that review corpus (26 of the 85 one-star reviews, behind 53 about broken output), so Tidy will not nag. What gets imported from
|
.jsbeautifyrc key (lonefy) |
Tidy setting | Notes |
|---|---|---|
indent_size |
tidy.indent |
Integer in [1, 16]; out-of-range values are dropped with a note. |
brace_style |
tidy.brace_style |
One of collapse, expand, end-expand, none, collapse-preserve-inline. |
wrap_line_length |
tidy.wrap_line_length |
Non-negative integer; 0 disables wrapping. |
wrap_attributes |
tidy.wrap_attributes |
One of auto, force, force-aligned, force-expand-multiline, aligned-multiple, preserve, preserve-aligned. |
space_after_anon_function |
tidy.space_after_anon_function |
Boolean. |
.jsbeautifyrc import is a best-effort convenience, not a marketing promise. Most lonefy users ran it with no config at all. Tidy also reads your existing VS
Code settings (editor.tabSize, editor.insertSpaces, per-language overrides)
directly, so in most cases there is nothing to port over.
What lonefy breaks → what Tidy fixes
Every row below is sourced from a real Marketplace review or GitHub issue on the incumbent. Everything inside quotation marks is verbatim: line breaks and runs of whitespace inside a review are rendered as a single space, a quoted fragment that starts or stops mid-sentence carries an ellipsis at that end (a quote that is one or more whole sentences lifted from a longer review carries none), and no word, spelling or filename is changed. Anything not in quotation marks is Tidy's own summary of the linked source.
| What JS-CSS-HTML Formatter does | What Tidy does instead | Source |
|---|---|---|
Formats on save even when you turned formatOnSave off, "Does not respect onSave: false." / “Always formats on save regardless of "editor.formatOnSave"”. |
Tidy never hooks save at all. Format-on-save is owned entirely by VS Code; if editor.formatOnSave is off, nothing happens on save. |
Review "Does not respect onSave: false"; issue #143, #131 |
| Makes itself the default formatter and overrides other formatters, "Formats code when you save it, regardless of your default formatter." / "Prevents every other formatter from working." | Tidy never contributes editor.defaultFormatter in its manifest, and never writes it without an explicit confirmation from you. If another formatter is your default, Tidy stays out of the way and works alongside it. |
The two 1★ reviews quoted at left; issue #57, "plugin ignores all other editor settings" |
Breaks JSX, "It formats <App /> to < App / >". |
Tidy formats JSX/TSX with a real parser (Prettier under the hood for those languages), and a safety guard rejects any output where a JSX tag boundary was mangled, even when the broken output still happens to re-parse. | The 1★ review quoted at left; issues #64, #76 |
Corrupts modern operators, “Changing my valid "?." to "? ." on save” and big-int literals like 1n turned into 1 n, producing syntax errors. |
These are preserved exactly. If any engine ever produced output that changed program meaning, the AST-equivalence guard discards it and leaves the file untouched. | Issue #146, title quoted at left; issue #150, "breaks Null coalescing operator"; a 1★ review: "… big integer number like 1n are formatted as 1 n by it, which results in a syntax error" |
| Ignores your indent settings, "When saving using 2 spaces instead of tabs, it changes to 4 spaces." / "cannot set to 2 spaces". | Tidy honours editor.tabSize / editor.insertSpaces and the live FormattingOptions VS Code passes in, so 2-space projects stay 2-space with no config file. |
Issues #31, #100, #73 |
| Adds stray spaces in HTML attributes / class & id names, "… every time I save the file it just randomly add space to some class name or id name." | The HTML guard re-tokenises and tree-compares output (parse5); any change that alters the tree is rejected, so attribute and identifier text cannot drift. | Reviews "… randomly add space to some class name or id name …" and "… it would add whitespaces after all of my ids and classes …"; issue #106, "HTML file auto add unexpected spaces in double quotes string value" |
Breaks SCSS/CSS, "SASS interpolation breaks CSS calc() statement", and wraps mixins with +. |
CSS/SCSS/LESS output is verified against a PostCSS tree before it is applied; structurally different output is dropped rather than written. | Issue #74; review about calc(#{…}) interpolation |
| Reformats files you never want touched, "It keeps re-formatting my packages.json, which is the last file you ever want to re-format." | Tidy only ever formats the document you explicitly invoke it on. It hooks no save event and creates no file watcher, and no editor event can make Tidy format, edit or write anything: the only two listeners it registers anywhere (active editor changed, settings changed) do nothing but repaint the status bar. Nothing is reformatted in the background. | The 1★ review quoted at left; issue #134, "Json formatting is set to true by default!" |
| Cross-file corruption, "rewrites another files after copy-paste", "Reproducible bug, copying contents of files on file save". | Each format call returns edits for a single document only; there is no path by which one file's content can land in another. | Issues #56, #29, #102, #110 |
Before / after
These are real inputs from this repo's samples/ folder, run through Format Document. The point is not just that the output is tidy; it is that nothing in your code changed meaning along the way. Both "after" blocks are what Tidy's own dispatchFormat really prints at the documented default tab size of 4, character for character (the code fence's own surrounding newlines aside). The unit suite re-runs that pipeline on the same samples/ files and fails if either block drifts.
CSS: collapsed, comma-jammed rules become readable, and the combinator and calc() survive intact:
/* before, samples/messy.css */
.card{display:flex;padding:8px;color:red}
#main > .item:nth-child(2n){margin:calc(100% - 20px);background:blue}
a:hover{text-decoration:underline} .footer { gap : 12px }
/* after, at the default tab size of 4 */
.card {
display: flex;
padding: 8px;
color: red
}
#main>.item:nth-child(2n) {
margin: calc(100% - 20px);
background: blue
}
a:hover {
text-decoration: underline
}
.footer {
gap: 12px
}
TSX: note what does not happen: <div className="card" … /> stays a valid JSX element (no < div / >), and n?.toString() ?? "none" keeps its optional-chaining and nullish-coalescing operators exactly:
// before, samples/messy.tsx
import {useState,useEffect} from "react"
type Props={title:string,count?:number}
export function Widget({title,count=0}:Props){
const [n,setN]=useState(count);useEffect(()=>{console.log(n?.toString()??"none")},[n])
return <div className="card" onClick={()=>setN(n+1)}><h1>{title}</h1><span>{n}</span></div>
}
// after
import { useState, useEffect } from "react";
type Props = { title: string; count?: number };
export function Widget({ title, count = 0 }: Props) {
const [n, setN] = useState(count);
useEffect(() => {
console.log(n?.toString() ?? "none");
}, [n]);
return (
<div className="card" onClick={() => setN(n + 1)}>
<h1>{title}</h1>
<span>{n}</span>
</div>
);
}
If either output had altered the parse tree (a mangled JSX tag, a ?. split into ? .), the equivalence guard would have discarded it and left your file untouched (see Safety guarantees below).
Safety guarantees
Tidy's core promise: we will never write a file that fails an equivalence check against your original. Concretely:
- AST-equivalence guard (JS / TS / JSX / TSX). After formatting, Tidy parses
both the input and the output and compares them modulo whitespace and style.
If they are not semantically equivalent, Tidy returns zero edits and your
file is left exactly as it was. This includes a dedicated JSX tag-boundary
check, so a mangled
< Foo bar = {x} />is rejected even though it still re-parses as valid TSX. A parse-only check would let that corruption through; Tidy does not. - Re-tokenise + tree compare (CSS / SCSS / LESS / HTML). For languages without a strict parse-or-fail AST, Tidy compares a PostCSS tree (CSS family) or a parse5 tree (HTML). Any output whose tree differs from the input's is discarded.
- Value-level check (JSON / JSONC). The parsed value must be unchanged.
- Idempotence, verified in the test corpus, not per format.
format(format(x))equalsformat(x)for every fixture in the corpus, so your code does not drift to the right every time you reformat. To be exact: this is a property the test suite proves, not a check run on each format. Nothing is formatted twice at runtime to verify it, and no output is ever rejected for failing it. The checks that do run before every write are the equivalence checks above. - Single-document isolation. A format call only ever produces edits for the document you invoked it on. No background watchers, no cross-pane copying.
- No silent failures. When the guard aborts, you get a non-blocking notice
and a line in the Tidy Formatter output channel explaining why. That line is
exhaustively: an ISO timestamp, the file's name, its language id, the engine id,
the status, whether it was a selection, and one short sanitized reason (which,
for a file skipped by an ignore rule, is the path of the
.soukformatignorethat excluded it). Tidy never copies your document into it, and the reason is cut to its first line and capped at 200 characters before it is stored or printed, so a parser error's multi-line code frame (the excerpt of your file a parser prints under its message) can never reach it. What that cut keeps is the parser's own first line, and a parser sometimes names the token it choked on there, so that one short line can quote a few characters of the construct that failed. Nothing else of your file is ever recorded. Nothing is persisted and nothing leaves your machine.
If the guard ever aborts a format you expected to succeed, that is by design: the file is intact, and the alternative (writing potentially corrupted output) is exactly the failure mode Tidy exists to prevent.
Fast startup, by design
Tidy's heavy machinery does not ship in the file VS Code loads at activation.
The Prettier printer and the three parser sets backing the safety guard
(@babel/parser, PostCSS, parse5) live in four separate chunks, each loaded the
first time a format actually needs it. Opening a file evaluates only the small
host bundle: no chunk is loaded to open, read or scroll a document. The one thing
that can evaluate a chunk without a format is the idle preloader described just
below, five seconds after activation, and only for the languages already on
screen at that instant.
Five seconds after activation, a one-shot timer reads the languages of the
editors visible on screen at that instant (window.visibleTextEditors: a file
sitting in a background tab is not among them) and warms only the chunks those
languages can reach:
| Visible editor | What the preloader warms |
|---|---|
| CSS, SCSS, LESS | the PostCSS verifier, nothing else |
| HTML | parse5. An embedded <script> or <style> pulls the JS or CSS verifier on first use, and only for a document that really contains one |
| JSON, JSONC | nothing at all: their printer and their value check are already in the host bundle |
| JavaScript | the Babel verifier |
| TypeScript, TSX, JSX | the Prettier printer and the Babel verifier |
A session with only stylesheets open therefore warms the PostCSS verifier and
nothing else. The chunk that carries the TypeScript printer is never preloaded
for such a session, and it is evaluated only when something really reaches it:
a TypeScript/TSX/JSX file, a .js file that turns out to contain JSX, or the CSS
second-chance fallback. That last one is worth spelling out, because it means
opening only stylesheets is not by itself a promise the chunk stays unloaded: if
the guard rejects a js-beautify stylesheet result, Tidy makes one independent
second attempt through Prettier's PostCSS printer, which ships in the very same
chunk as the TypeScript printer, and loading it then is exactly what the design
intends.
The timer is not an event hook: it reads that list of languages once and never again, and registers no listener and no watcher. Closing or reloading the window before it fires cancels it and leaves no work behind. Files you open afterwards are not preloaded; they simply load their chunk on first use, like everything else.
This changes nothing about safety. The guard runs the exact same verifier code, always loaded before any write. If a chunk fails to load, Tidy aborts that format and leaves your file untouched, and reports it as an engine failure rather than as a rejection of your code: a verifier that could not run proves nothing about what you wrote, and is never treated as a verifier that passed. A chunk that fails to preload is only noted in the Tidy Formatter output channel; it loads normally on first use.
60-second opt-in setup
Tidy does nothing on save until you opt in. That is deliberate: it is the opposite of the incumbent's "it formats whether you like it or not". Two independent choices:
1. Format manually (no setup needed).
Open any supported file and run Format Document (Shift+Alt+F) or select code and run Format Selection. If Tidy is the only formatter for that language it just works; otherwise pick it once via Format Document With… → Tidy Formatter.
2. Make Tidy your default formatter (optional).
Right-click in an editor → Format Document With… → Configure Default Formatter… → choose Tidy Formatter. Or in settings.json, per language:
{
// Example: use Tidy for CSS, keep Prettier for everything else.
"[css]": { "editor.defaultFormatter": "ced-lab.tidy-formatter" }
}
3. Turn on format-on-save (optional).
The Tidy: Use Tidy as My Formatter command offers this as a guided step: it
lists the exact editor.formatOnSave keys it would write for your chosen
languages, writes only after you confirm, and gives you a one-click Revert.
Prefer doing it by hand? It is the stock VS Code setting:
{
"editor.formatOnSave": true
}
You can scope it per language so Tidy only runs on the languages you want:
{
"[css][scss][less]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "ced-lab.tidy-formatter"
}
}
That's it: there is no Tidy-specific "format on save" toggle, because Tidy delegates the entire save lifecycle to VS Code. The guided step above writes that same stock setting, nothing custom, and only for the languages you confirmed.
"It does nothing?" If you installed Tidy and saving no longer reformats, that is expected: Tidy never auto-formats. Complete steps 2 and 3 above to choose the behaviour you want.
Supported languages
| Language | Engine | Safety guard |
|---|---|---|
| CSS, SCSS, LESS | js-beautify | PostCSS tree compare |
| HTML | js-beautify | parse5 tree compare + template-island preservation |
| JSON, JSONC | jsonc-parser (not js-beautify) | parsed-value compare |
| JavaScript | js-beautify (real parser if JSX is detected) | AST equivalence (@babel/parser) |
| TypeScript | real parser (Prettier) | AST equivalence |
| TSX (TypeScript React) | real parser (Prettier) | AST equivalence |
| JSX (JavaScript React) | real parser (Prettier) | AST equivalence |
Plain .js files are formatted with js-beautify for familiarity, but if Tidy detects JSX inside them it automatically re-routes to the real parser rather than emit broken output.
Tidy also reads your project's .editorconfig cascade (honouring root = true) and maps indent_style, indent_size / tab_width, end_of_line, insert_final_newline, trim_trailing_whitespace and max_line_length. A team .editorconfig therefore wins over plain VS Code settings. Opt out with "tidy.editorconfig": false. In Restricted Mode the workspace .editorconfig is ignored (it is workspace-authored content), falling back to built-in and user settings.
For the files Prettier actually prints (TypeScript, TSX, JSX, and a plain .js file that turns out to contain JSX), Tidy also honours your project's existing Prettier config: the data variants (.prettierrc, .prettierrc.json/.json5/.yaml/.yml/.toml) or the prettier key of package.json, found in Prettier's own search order (nearest config wins, package.json first) but never above the workspace folder the file lives in. Only ten AST-safe stylistic keys are read (printWidth, tabWidth, useTabs, semi, singleQuote, jsxSingleQuote, trailingComma, bracketSpacing, bracketSameLine, arrowParens); anything else, including parser, plugins and per-glob overrides, is ignored with a note in Show Effective Configuration, and a prettier key that names a shareable config instead of spelling out options is not resolved. Code-based configs (.prettierrc.js, prettier.config.mjs, ...) are never executed: when one is the nearest config, Tidy skips the layer entirely rather than guessing. The layer sits between .editorconfig and .soukformatrc, so your .soukformatrc keeps the last word, with one documented exception for line width (see below). It applies to saved files inside a workspace folder (an untitled buffer has no project to read). Opt out with "tidy.prettier.respectProjectConfig": false. Always ignored in Restricted Mode.
Known deviation, line width. One end-to-end case does not follow the layer order above, and it is better stated than hidden. For the files Prettier prints, Tidy sets the printer width from the shared width option first (tidy.wrap_line_length, which is also where .editorconfig's max_line_length and a .soukformatrc wrap_line_length land), then overwrites it with tidy.prettier.printWidth whenever that key has a value. So a tidy.prettier.printWidth sitting in plain VS Code settings beats an .editorconfig max_line_length and a .soukformatrc wrap_line_length, even though both are higher layers. Measured on an 88-character line: max_line_length = 120 alone leaves it on one line, but adding "tidy.prettier.printWidth": 40 wraps it at 40, and a .soukformatrc asking for 120 wraps at 40 too.
Nothing else deviates, and the workaround is exact: express the width in the same vocabulary as the layer you want to win. A printWidth written in .soukformatrc (or in your .prettierrc) does take precedence over the VS Code setting, because it is then the same key being layered. The real fix is to resolve one width value before the engine runs; it changes the output of projects that are already configured, so it ships in the next minor release rather than in this patch.
.sass (indented syntax) and Vue/Astro single-file components are not in this build; they are on the roadmap for a future release.
Languages & how Tidy formats each
Tidy handles every language below the same consent-first way: it reformats a file only when you run Format Document (Shift+Alt+F), Format Selection, or Tidy: Preview Format (Diff), and you can make it the default for any subset of these languages in one step with Tidy: Use Tidy as My Formatter (tidy.useAsFormatter). Tidy declares activation on those ten languages and nothing else; since VS Code 1.74 an extension is additionally activated when one of its contributed commands is invoked, which is always something you started from the Command Palette or the status bar menu. Here is what each family covers.
HTML
Tidy is a full HTML formatter and beautifier: it re-indents tags, normalises attribute spacing, and wraps long attribute lists the way you configure them (tidy.wrap_attributes). Trigger it with Format Document on any .html file, or set it as your HTML default via Tidy: Use Tidy as My Formatter. What separates it from the old JS-CSS-HTML Formatter is the parse5 tree comparison that runs after every format: if beautifying would add a stray space to a class or id name, or otherwise alter the document tree, Tidy discards the result and leaves your file untouched.
Two things inside an HTML file get different treatment, and it is worth being exact about which. Templating syntax ({{ … }}, {% … %} and <% … %>) is preserved byte-for-byte: each island is masked out before the engine runs and restored verbatim, so a beautifier can never inject whitespace inside a template tag. Embedded <script> and <style> bodies are not left alone: they are reformatted too, which is exactly what tidy.indent_scripts controls. What protects them is the guard rather than a mask, and it reads them as code: a <style> body is compared as a PostCSS tree, a JavaScript <script> body as a Babel AST plus its comment list (so a dropped licence header or eslint-disable is caught), and a type="…json…" body as a parsed value. A pure reindent therefore passes, and any change of meaning is rejected. A body whose declared language will not parse is held to a byte-identical comparison instead, never accepted on a guess.
A third thing decides whether Tidy writes at all: whether the whitespace it moved is drawn. HTML says so in three ways, and the guard models all three as one rule. The preformatted tags (<pre>, <textarea>, <xmp>, <listing>, <plaintext>) are handed to the printer as no-go zones and compared byte for byte. xml:space="preserve" (SVG) and the CSS property white-space (pre, pre-wrap, pre-line, break-spaces, or white-space-collapse: preserve) are read off the element, inherited by its whole subtree, and switched back off only by a declaration of their own kind: white-space: normal on a child cancels an inherited white-space: pre, never a <pre>. pre-line is modelled as what it is, collapsing runs of spaces while drawing newlines, so reflowing its spaces is accepted and losing a line break is not. The printer knows none of this (js-beautify's content_unformatted takes tag names only), so wherever whitespace is drawn and the printer moved it, Tidy declines the format and leaves the file byte-identical rather than write a page whose text a reader would see change. One case is refused rather than analysed: when an embedded <style> sets a preserving white-space value, deciding which elements its selectors match would take a CSS selector engine, so Tidy refuses that document instead of betting the rule matched nothing it touched. A stylesheet reached through <link> is not in the file at all, and is outside what any file-local check can see.
CSS, SCSS & LESS
CSS, SCSS and LESS are all first-class: Tidy beautifies collapsed rules into readable blocks, aligns declarations, and controls selector and rule spacing (tidy.selector_separator_newline, tidy.newline_between_rules, tidy.space_around_combinator). Run Format Document on a .css, .scss or .less file, or opt in per language with Tidy: Use Tidy as My Formatter. Every result is checked against a PostCSS tree before it is written, so fragile constructs like calc() and SCSS interpolation (#{…}) survive formatting instead of being mangled.
JavaScript & TypeScript
For JavaScript, Tidy uses js-beautify for a familiar layout and honours your indent settings (editor.tabSize / editor.insertSpaces) with no config file needed; TypeScript is formatted by Prettier under the hood. Format either with Format Document, or assign Tidy as the default formatter for those languages with Tidy: Use Tidy as My Formatter. Whichever engine runs, an AST-equivalence guard compares the parse tree before and after, so modern syntax such as optional chaining (?.), nullish coalescing (??) and BigInt literals (1n) is preserved exactly, and any output that would change program meaning is thrown away rather than written.
JSX & TSX (React)
React files, .jsx and .tsx, are formatted with Prettier, the same engine most React teams already trust, so JSX elements reflow correctly instead of <App /> turning into < App / >. Use Format Document, or pick these languages in Tidy: Use Tidy as My Formatter to make Tidy their default. On top of the AST check, a dedicated JSX tag-boundary guard rejects any output where a tag boundary was mangled (even when the broken result still happens to re-parse as valid TSX), and a plain .js file that actually contains JSX is automatically re-routed to the real parser rather than risk broken output.
JSON & JSONC
Tidy formats both strict JSON and JSON-with-comments (JSONC): it re-indents and normalises structure while keeping comments intact for JSONC. Trigger it with Format Document, or enable it as your JSON default through Tidy: Use Tidy as My Formatter. Because the parsed value is compared before and after, formatting can never change the data your JSON represents, and since Tidy only ever touches the file you invoke it on, it will not reformat package.json behind your back.
Works alongside Prettier
Tidy is a configurable beautifier, not an opinionated replacement for Prettier, and it is built to sit next to it, not on top of it.
- Tidy never declares itself your default formatter. It never contributes
editor.defaultFormatterin its manifest, and never writes it without an explicit confirmation from you (the one command that writes it is Tidy: Use Tidy as My Formatter, and before writing anything it shows you a modal recap of the exact"[lang]": { "editor.defaultFormatter": "ced-lab.tidy-formatter" }keys, the scope they go to, and the name of any formatter each one would replace; a language already pointing at Tidy is listed as such and is not rewritten). So it never silently takes over a language Prettier already owns. (The incumbent did the opposite: a 1★ review reports "Prevents every other formatter from working.", and issue #57 is titled "plugin ignores all other editor settings".) - You assign each language yourself. A common setup is Prettier for JS/TS and Tidy for CSS/HTML/JSON. Because nothing competes for the default slot, that split is just two per-language
editor.defaultFormatterentries; both formatters stay installed and active, each on the languages you chose. - For TS, JSX and TSX, Tidy is Prettier under the hood. Those languages are formatted by the same Prettier engine, then checked by Tidy's AST-equivalence guard. So even when Tidy is the formatter you invoked, the actual reflow on your React/TypeScript code is Prettier's; there is no second, conflicting style to reconcile.
In short: keep Prettier exactly as it is. Tidy only ever runs on the languages you explicitly hand it, and it gets out of the way everywhere else.
Ignore & coexistence
Tidy gives you fine-grained control over what it touches and gets out of the way when another formatter already owns the project (three things the abandoned incumbent never did; the leftmost column cites the issue or review that shows it). None of this can corrupt a file: every protected span is restored verbatim, so the equivalence guard sees output equal to the input and accepts it; if a splice ever produced non-parsable output the guard rejects it and your file is left intact.
| Capability | lonefy (#16, abandoned 2017) | Tidy |
|---|---|---|
| Skip a region / node in-source | ✗ none (#16) | tidy-ignore-start/end, // tidy-ignore, // prettier-ignore |
| Exclude files project-wide | ✗ none | .soukformatignore (gitignore syntax) |
| Step back for Prettier/Biome/dprint/ESLint | ✗ takes the default slot: a 1★ review reports "Prevents every other formatter from working.", and issue #57 "plugin ignores all other editor settings" | one-shot deference notice; never changes editor.defaultFormatter |
In-source ignore directives
Write a comment (in the host language's syntax) to keep part of a file exactly as authored:
/* tidy-ignore-start */
.keep { color : red ; } /* this block is preserved BYTE-for-BYTE */
/* tidy-ignore-end */
.rest { color: blue; } /* …while everything else is reformatted */
- Whole file: a head comment containing
tidy-ignore-file,tidy-ignore, orprettier-ignore(the first significant comment at the top) leaves the entire document untouched. One deliberate exception: a head comment that carriestidy-ignore-startortidy-ignore-endis read as a region marker, never as a file-ignore, so a file whose very first comment is/* tidy-ignore-start */protects that region and formats the rest of the document normally. - A region:
tidy-ignore-start…tidy-ignore-endpreserves everything in between, verbatim (markers included). An unterminated region protects to end-of-file (conservative: it protects more, never less). - The next node: a lone
// tidy-ignoreor// prettier-ignorecomment protects the node on the following line (best-effort, conservative line heuristic).
Engine coverage. For TS/TSX/JSX (the Prettier path) // prettier-ignore is honoured natively at the node level by Prettier itself, the richer, syntax-aware behaviour. For CSS/SCSS/LESS/HTML, JSON/JSONC and javascript (js-beautify, and jsonc-parser for JSON/JSONC) Tidy protects tidy-ignore-* regions itself via a mask-and-restore step behind the guard. That list is a list of language ids, not of engines, so it is not the same as "every path Prettier does not print": a .js file that contains JSX is routed to Prettier and is still region-masked by Tidy, and on that one path both mechanisms apply. Node-level masking on that path is best-effort: if a coarse span can't be spliced back safely, the guard simply rejects and the file stays intact (never corruption). Node-level directives are richest on the Prettier path; this mask-and-restore limitation is documented rather than risked.
.soukformatignore
Drop a .soukformatignore at your project root (or any sub-folder) to exclude files from formatting entirely: Tidy leaves them byte-identical. It uses familiar gitignore syntax:
# vendored / generated, never reformat
*.min.css
dist/
vendor/**/*.js
# …but DO keep this one tidy (negation re-includes it)
!vendor/keep.css
- Last matching pattern wins; a trailing
!negationre-includes a previously-excluded path (git semantics). - The lookup walks up from the file and stops at the first
.soukformatignoreit finds: the nearest one wins outright. A.soukformatignorefurther up (your project root) is not merged into it, and is not consulted at all once a nearer one exists. Patterns are always matched relative to the folder holding the file that won. - It is read only in trusted workspaces (ignored in Restricted Mode → the file is formatted). If the ignore file can't be read, Tidy fails safe and formats normally.
- Turn it off entirely with
"tidy.respectSoukformatignore": false.
Preview command (read-only diff + atomic undo)
Run Tidy: Preview Format (Diff) (tidy.previewFormat) from the Command Palette to see exactly what Tidy would change before committing to it:
- Opens a read-only side-by-side diff (original vs. formatted). Opening it writes nothing; your file's dirty state is unchanged.
- The preview runs the same pipeline as a normal format, including the equivalence guard. If the guard would reject the output, no diff opens and you get a non-blocking notice (file intact).
- Click Apply to write the result as a single undo entry (one
Ctrl+Zfully reverts it). Dismiss the prompt and nothing is written. - The diff is read-only, but the original document is not: you can keep typing in it while the diff is open. Apply therefore re-checks, at the last moment before writing, that the document is still byte-for-byte the text the guard verified. If it moved on, nothing is written and Tidy asks you to preview again, rather than overwriting your edits with output that was never checked against them.
- The preview honours the same pre-checks as Format Document, in the same order: the per-language
tidy.<lang>.enableswitch, the size cap,.soukformatignore, and top-of-file ignore markers. When one of them applies, Tidy tells you which and opens no diff, so a preview can never promise a change a real format would refuse.
No file at hand? Run Tidy: Open Playground first: it opens the two messy samples from this README as untitled scratch buffers, ready to preview.
Status bar
For supported files, Tidy shows a single status bar item on the right. At rest it reflects the
active file's state: ready, turned off for the language, excluded by .soukformatignore, over
the tidy.maxFileSizeKB limit, or owned by another default formatter (named in the tooltip,
with how to switch). Right after a format attempt it briefly shows what happened
(formatted, already tidy, guard blocked, or the exact skip reason) before returning to
the passive state, so a silent no-op can no longer look like a broken extension.
Clicking the item opens a menu over three existing commands, under the menu's own
labels: Explain last format, Preview format (diff) and Show effective
configuration (the Command Palette lists the last one under its command title,
Tidy: Show Effective Configuration). The item is purely observational: it never
formats, never edits and never writes a setting. After its first paint
when Tidy activates, it repaints on exactly three occasions: you switch to another editor, a
setting in the tidy section or editor.defaultFormatter changes (a change to any other
setting is ignored), or a format attempt you invoked has just finished (it then shows the transient
result and returns to the passive state on its own). The first two are the only editor
events Tidy subscribes to anywhere in the extension
(window.onDidChangeActiveTextEditor and workspace.onDidChangeConfiguration); both do
nothing but redraw this item, so no editor event can start a format or produce an edit. The bar
and its tooltip carry this and nothing else: the active file’s language id, the engine id,
the status, a timestamp, whether the attempt was a selection, the same short sanitized
reason the output channel gets and, in the states that need one, the extension id of the
formatter that owns the language instead of Tidy, the size cap in KB, and the name of the
setting key involved (tidy.<lang>.enable, tidy.maxFileSizeKB or
tidy.json.maxFileSizeKB). Never your file’s name, never a line of its content, and a
parser message only as its first line, cut at 200 characters. Hide it with tidy.statusBar.enable: false.
Deference behavior (coexisting with other formatters)
When a workspace already configures another formatter or fixer (Prettier via .prettierrc* or a prettier key in package.json, Biome via biome.json, dprint via dprint.json, or ESLint via .eslintrc* / eslint.config.* or an eslintConfig key in package.json), Tidy surfaces that fact once, and otherwise stays out of the way. ESLint is called out as what it is: it does not compete for the formatter slot, so the notice says its changes arrive through its own eslint --fix runs rather than implying a conflict. Controlled by tidy.deferToOtherFormatters (default notify):
| Value | Behavior |
|---|---|
notify (default) |
Show a one-time, informational notification per workspace naming the formatters and fixers it found (Prettier, Biome, dprint, ESLint). Deduplicated via globalState, never a repeat nag. |
silent-defer |
Acknowledge the competitor without notifying. Still writes nothing and disables nothing. |
off |
Ignore detection entirely (no read, no notification). |
The anti-hijack contract (non-negotiable): deference never changes editor.defaultFormatter, never disables Tidy silently, and never writes any setting on its own. The notification is purely informational: it reminds you that you can disable Tidy per language (tidy.<lang>.enable) for this workspace if you prefer the other tool to own formatting. The choice, and any write, is always yours. A .prettierignore on its own does not trigger deference (it only narrows what Prettier would touch, it doesn't configure Prettier as a formatter), and .eslintignore is excluded for the same reason. Detection reads workspace files only in trusted workspaces. Detecting a Prettier config and reading one are two separate things: the .prettierrc layer described under Supported languages shapes Tidy's own output when you invoke it, while detection here only ever produces this one notice.
Tip: run Tidy: Show Effective Configuration to see, for the active document, whether it would be skipped (
.soukformatignore/ in-source marker), how many regions are protected, and which competing formatters were detected.
Key settings
All settings live under the tidy.* namespace. Indentation defaults come from your editor (editor.tabSize / editor.insertSpaces); the tidy.* values below are fallbacks used only when neither the editor nor a project config provides one.
| Setting | Default | What it does |
|---|---|---|
tidy.<lang>.enable |
true |
Per-language switch (tidy.css.enable, tidy.typescript.enable, …). Set to false to remove Tidy as a formatter for that language. |
tidy.indent |
4 |
Fallback indent size (js-beautify indent_size) when the editor provides none. |
tidy.brace_style |
collapse |
Brace placement (collapse, expand, end-expand, none, collapse-preserve-inline). |
tidy.wrap_line_length |
0 |
Max characters per line before wrapping; 0 disables wrapping. For the files Prettier prints, tidy.prettier.printWidth overrides it (see Known deviation, line width). |
tidy.wrap_attributes |
auto |
How HTML attributes wrap (auto, force, force-aligned, force-expand-multiline, aligned-multiple, preserve, preserve-aligned). |
tidy.space_after_anon_function |
false |
Add a space after an anonymous function keyword. |
tidy.maxFileSizeKB |
5120 |
Skip (with a notice) documents larger than this, so huge files never freeze the editor. 0 disables the guard. For JSON and JSONC, tidy.json.maxFileSizeKB takes precedence. |
tidy.json.maxFileSizeKB |
10240 |
Size cap for JSON/JSONC only; wins over tidy.maxFileSizeKB for these two languages. JSON formats on a fast linear path with a cheap value-equality guard, so big files like package-lock.json format instead of being skipped. 0 disables the cap. |
tidy.editorconfig |
true |
Read the project's .editorconfig cascade and let it override plain VS Code settings. Set to false to ignore .editorconfig. |
tidy.prettier.respectProjectConfig |
true |
Read the project's Prettier config (data variants of .prettierrc* or the prettier key in package.json) for the files Prettier prints (TypeScript/TSX/JSX, plus a .js file containing JSX), layered between .editorconfig and .soukformatrc. Data files only: code-based configs are never executed. Set to false to ignore it. Always ignored in Restricted Mode. |
tidy.soukformatrc |
true |
Read the project's .soukformatrc file (JSONC) for per-language options and glob overrides, layered above .editorconfig. Set to false to ignore it. Always ignored in Restricted Mode. |
tidy.respectSoukformatignore |
true |
Skip files matched by a project .soukformatignore (gitignore syntax), leaving them byte-identical. Set to false to format every file regardless. Always ignored in Restricted Mode. See Ignore & coexistence. |
tidy.deferToOtherFormatters |
notify |
How Tidy reacts when another formatter or fixer (Prettier/Biome/dprint/ESLint) is already configured: notify (one-time notice), silent-defer (no notice), or off. Never changes your default formatter. See Deference behavior. |
tidy.statusBar.enable |
true |
Show the Tidy status bar item (passive file state + a short transient result after each attempt). Purely observational; see Status bar. |
The wider option set ("not enough options", fixed). Beyond the headline knobs above, Tidy exposes 21 js-beautify options by family plus 8 AST-safe Prettier stylistic options for TS/JSX: 29 style settings in all. That is a curated subset, not the whole of js-beautify, which declares 47 options across its core/CSS/HTML/JavaScript modules; keys such as end_with_newline, space_before_conditional, content_unformatted, void_elements, wrap_attributes_min_attrs, jslint_happy and templating are not exposed today. If one you need is missing, open an issue and say which. A few of the 29:
| Setting | Applies to | What it does |
|---|---|---|
tidy.preserve_newlines / tidy.max_preserve_newlines |
all | Keep existing blank lines (and cap how many). |
tidy.space_in_paren, tidy.break_chained_methods, tidy.keep_array_indentation, tidy.comma_first, tidy.operator_position |
JavaScript | Fine-grained js-beautify JS layout. |
tidy.selector_separator_newline, tidy.newline_between_rules, tidy.space_around_combinator |
CSS/SCSS/LESS | CSS rule + selector spacing. |
tidy.indent_inner_html, tidy.indent_scripts, tidy.wrap_attributes_indent_size |
HTML | HTML structure + attribute wrapping. |
tidy.prettier.singleQuote, tidy.prettier.semi, tidy.prettier.jsxSingleQuote, tidy.prettier.trailingComma, tidy.prettier.bracketSpacing, tidy.prettier.bracketSameLine, tidy.prettier.arrowParens, tidy.prettier.printWidth |
TS/TSX/JSX/JS | Stylistic only, quote style, semicolons, trailing commas, parens, line width. Each is AST-invariant, so the equivalence guard still accepts the result. |
Per-language overrides. All 29 style options (the two tables of tidy.* formatting knobs above, from tidy.indent through tidy.prettier.arrowParens) are language-overridable, so you can scope any of them to one language in your settings.json. The behaviour switches are not: tidy.<lang>.enable is already per-language by construction, and tidy.maxFileSizeKB, tidy.json.maxFileSizeKB, tidy.editorconfig, tidy.prettier.respectProjectConfig, tidy.soukformatrc, tidy.respectSoukformatignore, tidy.deferToOtherFormatters and tidy.statusBar.enable cannot be put inside a [language] block. Their reach is not uniform, either: tidy.prettier.respectProjectConfig, tidy.soukformatrc and tidy.respectSoukformatignore are declared resource-scoped, so in a multi-root workspace each folder can carry its own value and Tidy reads the one belonging to the file being formatted. The rest (tidy.<lang>.enable, tidy.maxFileSizeKB, tidy.json.maxFileSizeKB, tidy.editorconfig, tidy.deferToOtherFormatters and tidy.statusBar.enable) are window-scoped: one value for the whole window, and a folder-level override of them is ignored by VS Code.
{
"tidy.brace_style": "collapse",
"[javascript]": { "tidy.brace_style": "expand" },
"[typescript]": { "tidy.prettier.singleQuote": true }
}
Project config, .soukformatrc (JSONC). Drop a .soukformatrc at your project root to drive style per language (and per glob), shared across the team and layered above .editorconfig:
{
// per-language sections (omit the "tidy." prefix)
"css": { "indent": 2, "newline_between_rules": false },
"typescript": { "singleQuote": true, "semi": false },
// optional ordered glob overrides (most specific wins)
"overrides": [
{ "files": "src/**/*.css", "options": { "indent": 8 } }
]
}
A malformed .soukformatrc never breaks formatting: it is ignored with a warning and Tidy falls back to the lower layers. It is read only in trusted workspaces.
Diagnosing your config: run Tidy: Show Effective Configuration from the Command Palette to see every resolved option and the exact source of each value (VS Code global, tidy.<lang>, .editorconfig, .prettierrc, .soukformatrc, or a glob override). This is the direct answer to "why is it indenting with 4 spaces?", no guesswork, no hours of debugging.
Commands
All eight Tidy commands live in the Command Palette (Ctrl+Shift+P) under the Tidy
category, but three of them are gated on the active language and are listed only while a
file Tidy supports is focused: Show Effective Configuration, Preview Format (Diff) and
Explain Last Format all carry "enablement": "editorLangId in tidy.supportedLanguages" in
the manifest. The other five are always listed.
| Command id | Title | What it does |
|---|---|---|
tidy.showEffectiveConfiguration |
Tidy: Show Effective Configuration | Shows every resolved option and the exact source of each value. |
tidy.useAsFormatter |
Tidy: Use Tidy as My Formatter | Opt in per language: writes editor.defaultFormatter (Workspace by default) for the languages you pick, then optionally editor.formatOnSave for the same languages, each only after an explicit confirmation with an exact recap; the format-on-save write has a one-click Revert. Cancelling writes nothing. |
tidy.runMigration |
Tidy: Migrate from JS-CSS-HTML Formatter | Imports a legacy .jsbeautifyrc into tidy.* settings after a confirmation recap (trusted workspaces only), then optionally runs Use Tidy as My Formatter. |
tidy.previewFormat |
Tidy: Preview Format (Diff) | Opens a read-only diff of what Tidy would change, then applies it on an explicit Apply click as a single undo entry. Opening the diff writes nothing. See Preview command. |
tidy.openPlayground |
Tidy: Open Playground | Opens two untitled scratch documents (the messy CSS and TSX samples from this README) so you can try Preview Format (Diff) in 30 seconds without touching any file on disk. It opens editors and nothing else: no file is created, no setting is written. |
tidy.reportIssue |
Tidy: Report an Issue | Opens a prefilled GitHub issue with your environment (Tidy version, VS Code version, OS, the active file's language) already filled in, so a bug report takes seconds. Read-only: it opens a browser tab and writes nothing. |
tidy.explainLastFormat |
Tidy: Explain Last Format | Tells you what Tidy's most recent format did on the active file, or why it did nothing (safety guard, an ignore rule, the size limit, another default formatter, a disabled language). Per-file: it answers for the file you are looking at, not for whatever Tidy formatted last. It reads the same bounded session window as Show Format History (the 50 most recent attempts, all files together), so once 50 later attempts have pushed a file's record out of that window, Tidy reports that it has not formatted that file this session. Writes a content-free trace to the Tidy Formatter output channel. Read-only. |
tidy.showFormatHistory |
Tidy: Show Format History | A chronological view of this session's format attempts (the 50 most recent across all files, newest first): status icon, file, language and age. Older attempts are dropped, and the list starts empty again after a window reload. Picking an entry writes the full explanation to the Tidy Formatter output channel. In-memory only, content-free, read-only. |
FAQ
How do I format HTML, CSS and JavaScript in VS Code?
Open the file and run Format Document (Shift+Alt+F), or select a block and run Format Selection. VS Code needs a formatter registered for that language; Tidy provides one for HTML, CSS, SCSS, LESS, JavaScript, TypeScript, JSX/TSX and JSON. If more than one formatter is installed, pick which runs with Format Document With… → Tidy Formatter, or set it per language via Tidy: Use Tidy as My Formatter. Nothing formats automatically until you enable editor.formatOnSave yourself.
Is there a VS Code formatter that only runs when I ask, not on save?
Yes, that is Tidy's whole design. It registers only as an on-demand formatting provider (Format Document / Format Selection), never hooks the save event, and never sets editor.formatOnSave or editor.defaultFormatter behind your back: both are written only inside the guided opt-in command, after you confirm an exact recap of the keys, with one-click revert for format on save. So it formats exactly when you invoke it and stays silent otherwise. If you want format-on-save later, run that command or enable the stock VS Code setting yourself; see 60-second opt-in setup.
Why does VS Code keep formatting my code on save?
Something in your setup has editor.formatOnSave enabled and a default formatter assigned for that language, often an extension that made itself the default without asking. VS Code, not the extension, owns the save trigger, so the fix is to turn off editor.formatOnSave (globally or per language) or change editor.defaultFormatter. If code is still reformatted with format-on-save off, an extension is hooking save itself; disabling or uninstalling it is the only reliable fix. Tidy deliberately never hooks save, so it can never be the cause.
How do I stop an extension from formatting on save?
Start by setting "editor.formatOnSave": false; you can scope it per language, e.g. "[javascript]": { "editor.formatOnSave": false }. If code is still reformatted on save, the culprit is an extension that registers its own save handler instead of relying on VS Code's (the JS-CSS-HTML Formatter is a well-known example), and you have to disable or uninstall that extension from the Extensions view. No other extension, Tidy included, can turn off a save hook that a different extension installed.
Is there a safe alternative to JS-CSS-HTML Formatter? That is exactly why Tidy exists. It covers the same languages (JavaScript, TypeScript, JSX/TSX, CSS, SCSS, LESS, HTML and JSON) but never formats on save unless you turn that on, never makes itself the default formatter without asking, and never writes a setting unless you confirm it. Every format is verified against your original (AST for JS/TS/JSX/TSX, tree compare for CSS/HTML, value compare for JSON), so it cannot produce the corrupted output the incumbent was reported for. See Migrating from JS-CSS-HTML Formatter for a two-minute switch.
How is Tidy different from Prettier? Prettier is opinionated: it enforces one canonical style with very few knobs. Tidy is a configurable beautifier that respects your VS Code and project settings, so it fills the "lots of options" niche the JS-CSS-HTML Formatter used to. The two are built to coexist (a common setup is Prettier for JS/TS and Tidy for CSS/HTML/JSON), and for TypeScript, JSX and TSX Tidy actually runs Prettier under the hood, then adds its own equivalence guard on top. See Works alongside Prettier.
Does Tidy work in Cursor, Windsurf or VSCodium?
Tidy is a standard VS Code extension with no proprietary Marketplace dependencies, so it runs in editors built on VS Code (Cursor, Windsurf, VSCodium and similar). It is published on both the Visual Studio Marketplace and the Open VSX Registry, so editors that use Open VSX (VSCodium, Cursor, Windsurf, Gitpod, code-server…) can install it straight from their Extensions view, no manual .vsix needed. Nothing in the formatting pipeline is tied to a specific distribution.
Will Tidy break my JSX or modern JS?
No. JSX, TSX and TypeScript are formatted with a real parser (Prettier), not a token-level find-and-replace, so <App /> stays <App />. On top of that, the AST-equivalence guard, plus a dedicated JSX tag-boundary check, compares the parse tree before and after and discards any output that changed meaning, so optional chaining (?.), nullish coalescing (??) and BigInt literals (1n) are always preserved. If a format ever couldn't be proven safe, you get zero edits and an intact file.
Will Tidy format my files on save?
Only if you enable editor.formatOnSave and select Tidy as the default formatter for that language, either by hand or through the guided Tidy: Use Tidy as My Formatter command, which shows the exact keys it would write and does nothing until you confirm. Out of the box it does nothing on save, and it never installs a save hook of its own.
Will it override Prettier / ESLint / my chosen formatter?
No. Tidy never contributes editor.defaultFormatter in its manifest, and never writes it without an explicit confirmation from you: the only write happens inside Tidy: Use Tidy as My Formatter, for the languages you tick, after you confirm. If another formatter is your default, Tidy does not run unless you explicitly invoke it.
I installed it and nothing happens when I save. Is it broken? No, that is the intended default. If you are not sure what happened, run Tidy: Explain Last Format from the Command Palette: it tells you exactly what Tidy did on the active file, or why it did nothing (for example, another formatter owns the language, or the safety guard rejected the result). See 60-second opt-in setup to choose the behaviour you want.
Can it corrupt my code? Tidy refuses to apply any output that fails its equivalence guard (AST for JS/TS/JSX/TSX, tree compare for CSS/HTML, value compare for JSON). If formatting would change meaning, you get zero edits and an intact file plus a notice.
Does it support JSX and TypeScript? Yes, JSX, TSX, and TypeScript are formatted with a real parser, not a token mangler, and are covered by the AST guard.
Does it send my code anywhere? No. Tidy is 100% client-side: nothing your file contains is transmitted, persisted or written anywhere but back into your own document. It does not phone home, and the output-channel diagnostics carry no copy of your source (see Safety guarantees for exactly what a sanitized reason can contain).
Does it work in Restricted Mode / virtual workspaces / Codespaces? It declares support for untrusted and virtual workspaces. In Restricted Mode it falls back to built-in and user settings rather than workspace-defined config.
How do I stop Tidy from formatting a specific language?
Set tidy.<lang>.enable to false (e.g. "tidy.json.enable": false), or simply don't choose it as the default formatter for that language.
Can Tidy format large files like package-lock.json?
Yes. JSON and JSONC have their own size cap, tidy.json.maxFileSizeKB (default 10240 KB), which takes precedence over the generic tidy.maxFileSizeKB (default 5120 KB). JSON gets the higher default because Tidy formats it on a fast linear path and verifies the result with a cheap value-equality check, so a 10 MB package-lock.json formats well inside the performance budget instead of freezing the editor. Other languages keep the stricter cap because their engines and safety checks are heavier. Precedence in one line: an explicitly set tidy.json.maxFileSizeKB wins for JSON/JSONC; if it is unset, an explicitly set tidy.maxFileSizeKB still applies; otherwise each family uses its own default. If Tidy skips a file, run Tidy: Explain Last Format: it names the applied cap, its setting key and its source, so you know exactly which knob to raise. Set either cap to 0 to disable it.
How do I stop Tidy from touching a specific file or region?
Three ways, all covered in Ignore & coexistence: a .soukformatignore (gitignore syntax) excludes files project-wide; tidy-ignore-start/tidy-ignore-end comments preserve a region byte-for-byte; and a head tidy-ignore-file / // prettier-ignore comment skips the whole file.
Another formatter (Prettier/Biome) is already set up. Will Tidy fight it?
No. Tidy detects it and shows a one-time, informational notice (tidy.deferToOtherFormatters, default notify). That detection path writes nothing at all: it never changes editor.defaultFormatter and never disables Tidy silently; see Deference behavior.
Can I preview a format before applying it? Yes, run Tidy: Preview Format (Diff) for a read-only side-by-side diff, then Apply it as a single undo entry. See Preview command.
Does it read .editorconfig?
Yes. Tidy reads the .editorconfig cascade (respecting root = true) and maps the common keys (indent_style, indent_size/tab_width, end_of_line, insert_final_newline, trim_trailing_whitespace, max_line_length). Disable with "tidy.editorconfig": false.
Can I configure it with a project file?
Yes. Tidy reads a .soukformatrc (JSONC) from your project root, per-language sections plus ordered glob overrides, layered above .editorconfig and shared across the team. See Key settings for the schema. (YAML support is on the roadmap.)
Does Tidy read my .prettierrc?
Yes, for the files Prettier prints: TypeScript, TSX, JSX, and a plain .js file that contains JSX. Tidy reads the same data config files Prettier does (.prettierrc, .prettierrc.json/.json5/.yaml/.yml/.toml, or the prettier key of package.json), in Prettier's own search order but never above your workspace folder, and honours the ten AST-safe stylistic keys, layered between .editorconfig and .soukformatrc. Code-based configs (.prettierrc.js and friends) are never executed, and a shareable config reference is not resolved. Disable with "tidy.prettier.respectProjectConfig": false.
Where's the indented-.sass / Vue support?
Not in this build. The remaining roadmap covers .soukformatrc YAML, real CSS-in-JS reformatting, Vue SFCs, and more languages. Ignore directives, .soukformatignore, the preview diff, deference notifications, and Open VSX distribution are shipped, see Ignore & coexistence.
Feedback
Found a bug or a file Tidy got wrong? A reproducible input is worth a thousand stars: open an issue with the smallest snippet that reproduces it. The fastest way is the Tidy: Report an Issue command (Command Palette → Tidy: Report an Issue): it opens a prefilled GitHub issue with your Tidy version, VS Code version, OS and active language already filled in, so you only have to paste the snippet. If Tidy has saved you from a save-on-format disaster, you're welcome to leave a review. No in-product nagging, ever.
License
MIT. See CHANGELOG.md for release history.

