→ No extension to install — everything your editor knows about Salty comes through TypeScript.
Editor setup
There's no Salty extension to install and no language server to run. The compiler lives inside your bundler, and everything your editor knows about Salty arrives through TypeScript — which is the good news, because it makes this a short list: an editor that takes TypeScript seriously, the ESLint plugin, and a couple of scripts that keep the generated files generated.
All of it is once-per-project work. npx salty-css init covers part of it, but not in every project and not always all of it — so each step below is written so you can do it, or check it, by hand.
An editor with real TypeScript support
This is the one that matters most, because a large part of Salty's day-to-day feel isn't in the API — it's in what your editor can tell you about your own design system. And almost none of that type layer is hand-written. Your definitions generate it.
How you define it
import { defineVariables } from "@salty-css/core/factories";
export default defineVariables({
colors: { brand: { main: "#0070f3", highlight: "#ff4081" } },
spacing: { small: "8px", medium: "16px", large: "32px" },
});How you use it
import { styled } from "@salty-css/react/styled";
export const Card = styled("section", {
base: {
padding: "{spacing.large}", // ← type `{` and your token paths appear
background: "{colors.brand.main}",
},
variants: {
tone: { neutral: {}, brand: { color: "{colors.brand.highlight}" } },
},
});<Card tone="brand">Salty</Card>Three separate things just happened without you asking for them: the token paths autocompleted inside the style object, the wrong paths were greyed out, and tone became a typed union prop that accepts "neutral" or "brand" and nothing else. Add a media query or a template and the same applies to "@tabletDown" keys and textStyle: "heading.large" paths.
None of that is a Salty plugin. During each build the compiler writes a declaration file into saltygen/ describing your tokens, template paths and media query names, and your editor reads it like any other .d.ts. So the one thing that matters when picking an editor is how well it handles TypeScript. VS Code and WebStorm both do it out of the box, and anything else with a TypeScript language server attached reads exactly the same information. An editor without one still builds perfectly; you're just flying without instruments.
Nothing needs registering for .css.ts files either. They're TypeScript, your editor already knows what to do with them, and the suffix matters to the compiler rather than to your IDE.
Three settings worth checking
Point your editor at the project's TypeScript. Salty needs TypeScript 5.x, and most editors ship their own bundled copy that may be older than your project's. In VS Code: Command Palette → TypeScript: Select TypeScript Version → Use Workspace Version. WebStorm has the equivalent under its TypeScript settings.
Types follow the build. A fresh clone has no saltygen/ folder yet, so there's nothing to autocomplete from. Run the dev server once, or npx salty-css build, and the suggestions appear. The prepare script below is there so nobody on your team has to know this.
Your editor can hold a stale copy. Add a token, and the suggestion sometimes doesn't show up until the TypeScript server picks up the regenerated declaration file. Restarting it fixes it — Command Palette → TypeScript: Restart TS Server in VS Code — and knowing that up front saves you a few minutes of squinting at a token you're certain you defined.
What TypeScript won't catch
Worth saying plainly, because a page that only lists the wins sets you up to be surprised later: style values are permissive strings by design. That's what lets modifiers, custom syntax and one-off escape hatches work at all — and it also means "{colors.brnad.main}" is a perfectly valid string with no red squiggle under it.
The build catches that one instead, if you let it. Check that strict is on in your config — init writes it in when it generates the file, but a hand-written or older config may not have it:
import { defineConfig } from "@salty-css/core/config";
export const config = defineConfig({
strict: true,
});An unresolvable token path now fails the build rather than quietly emitting a declaration the browser drops. 'warn' is the middle setting if you're mid-migration. The rest of the options live in Configuration.
The ESLint plugin
Two mistakes in Salty are invisible to TypeScript because they're valid TypeScript: an unexported definition, and variants nested inside base. Both compile, both run, and both produce either no CSS or the wrong CSS with nothing anywhere telling you why. That's the entire reason the plugin exists, and it's why setting it up is the second thing on this page rather than an optional extra.
Set it up
Install the config, then extend it from your ESLint config file:
npm i -D @salty-css/eslint-config-core// flat config, ESLint 9+
import saltyCss from "@salty-css/eslint-config-core/flat";
export default [saltyCss];{ "extends": ["@salty-css/eslint-config-core"] }init can do this for you, but only when it finds one of those config files in the project folder or at the repo root, and only when it can work out where to insert the config — otherwise it says so and leaves the file alone. Open your ESLint config and confirm the line is actually there rather than assuming it is.
What it catches
must-be-exported — the compiler only collects exported definitions, so this is dead code as far as the build is concerned:
const Badge = styled("span", { base: { borderRadius: "999px" } }); // ✗
export const Badge = styled("span", { base: { borderRadius: "999px" } }); // ✓no-variants-in-base — variants belongs beside base, not inside it. Nested, Salty reads it as a selector and emits a rule targeting a child element literally named <variants>, while tone never becomes a prop at all.
Both rules are error by default, both are autofixable, and both bail out immediately on any file that isn't a Salty file — the rest of your codebase sees nothing from this plugin. The full walkthrough, including what the plugin deliberately doesn't do, is on Tooling.
Let the editor apply the fixes
Since both rules autofix, fix-on-save is the version of this you never have to think about again:
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}Other editors have their own equivalent; the point is that the missing export gets added while you're still looking at the file, not three hours later while you're inspecting an unstyled element in DevTools.
Scripts that keep the generated files generated
saltygen/ is a build artifact. It's regenerated from scratch on every compile, it's in .gitignore, and it holds both your stylesheet and the declaration file everything above depends on. Which means a fresh clone starts with neither — and the failure mode is confusing in exactly the wrong way: no token suggestions, and a page that renders unstyled, on a repo where everything is obviously committed correctly.
A prepare script is the fix. Open package.json and make sure this is there:
{
"scripts": {
"prepare": "npx salty-css build"
}
}prepare runs after npm install and npm ci, so cloning the repo and installing is enough to produce the CSS and the types before anyone starts a dev server. init adds it when it runs; if it isn't there, add it yourself.
Two places that still catch people out:
- CI or deploy platforms that install with
--ignore-scriptsskipprepareentirely. Addnpx salty-css buildto the build step explicitly there — it's also the honest way to run the compiler in isolation when you're debugging one. - Mixed package versions. Salty is pre-1.0 and
@salty-css/core,@salty-css/reactand your bundler plugin are versioned in lockstep. Mixing them produces confusing errors rather than clear ones, so bump them together withnpx salty-css up. When you're about to open an issue,npx salty-css --versionprints the CLI version plus every Salty package in yourpackage.json— paste that in.
While you're in the repo, two files worth getting right once: saltygen/ belongs in .gitignore, and .saltyrc.json — the small file that lets you run npx salty-css build from anywhere in the repo — is meant to be committed.