→ The map: every export grouped by the job it does, so you know what to reach for.
API overview
Salty's public surface is small enough to hold in your head: two ways to author component styles, a handful of define* factories, a couple of helpers, one config file, one CLI.
This page is the map. It doesn't take any single API apart — it answers the question you actually have on day two, which is "I want to do this thing, what do I even reach for?" Find the job, note the name next to it, click through when you need the detail. The pages behind those links are the Basics section, roughly one page per job, and that's where the real explanations live.
Everything follows the same two steps
Define something in a file the compiler reads, then reference it by name. That's it — tokens, breakpoints, templates, fonts, keyframes, all of them. Once you've seen it once, most of the API stops needing to be memorised.
Here's the define half — a couple of design tokens and a named breakpoint:
import { defineVariables } from "@salty-css/core/factories";
import { defineMediaQuery } from "@salty-css/react/config";
export const variables = defineVariables({
colors: { brand: { main: "#0070f3" }, ink: "#101014" },
});
export const tabletDown = defineMediaQuery((media) => media.maxWidth(900));And the use half:
import { styled } from "@salty-css/react/styled";
export const Card = styled("div", {
base: {
color: "{colors.ink}",
borderTop: "2px solid {colors.brand.main}",
padding: "2rem",
"@tabletDown": { padding: "1rem" },
},
});Note what isn't in that second file: any import of the things it uses. Definitions are global once they've reached the build, so a token path and an @name are just strings your editor happens to autocomplete. Both files end in .css.ts and export their calls — those two rules are the contract with the compiler, and breaking either one is most of what goes wrong early (Troubleshooting is ordered by how often each cause is the actual cause).
There are five reference syntaxes in total, and you now know two of them:
{colors.ink}— a variable.{theme.bg}— a variable whose value depends on the active theme.{props.tint}— a value the consumer passes at the call site ascss-tint."@tabletDown"— a named media query, used as a key.textStyle: "body"— a template, used as a key.
All of it resolves in Node while your project builds, and lands in one static stylesheet. The one deliberate exception is {props.X}, which rides a real CSS variable into the browser because that's the whole point of it.
Getting styles onto something
Four ways in, and the first one covers most days.
| I want to… | Reach for | Deep dive |
|---|---|---|
| Build a typed component whose variants are JSX props | styled | Styled api |
| Get a class string and put it on markup I already have | className | Class names |
Style the document itself — a reset, body, bare <a> | defineGlobalStyles, or global / reset in defineConfig | Global styles |
| Style something whose values don't exist until the request runs | defineRuntime | Runtime styles |
| Reach into a component from another component | nesting and selectors — no new API | Scoping and composition |
styled and className are the same styling surface with different ergonomics: variants, nesting, pseudo-classes, tokens, media queries and templates all work identically in both. One hands back a component, the other hands back a string.
Reusing a decision instead of retyping it
Four features cluster here, and they're easy to mix up because they all mean "don't write this again." The difference is what you're reusing.
| I want to reuse… | Reach for | Deep dive |
|---|---|---|
| A named value, shared and typed across the system | defineVariables + {token.path} | Variables & tokens |
| A whole set of values that swaps with context | defineVariables conditional scope + a data-theme attribute | Theming |
| A bundle of properties that always travel together | defineTemplates | Templates |
| A value shape recognised anywhere you write it | modifiers, registered in salty.config.ts | Modifiers |
| A computation that turns arguments into a value | a helper — your own function, or a built-in | Helpers |
Theming is the one worth flagging early even if you don't need it yet, because it's the cheapest thing on this list to build in from the start and the most annoying to retrofit. A conditional token group compiles to [data-theme="dark"] { --theme-bg: … } in the stylesheet, so flipping one attribute on an ancestor repaints everything under it through the browser's own cascade. No provider, no context, no re-render.
Making styles react to something
Salty's bias here is worth absorbing once: let the platform see the state if it can. A lot of what gets built with React state is something the browser is already tracking and will style for free.
| Reacting to… | Reach for | Deep dive |
|---|---|---|
Viewport width, print, prefers-reduced-motion, prefers-color-scheme | defineMediaQuery + "@name", or a raw "@media (…)" key | Breakpoints & responsive layouts |
| The size of a parent box rather than the screen | containerType + a "@container (…)" key | Breakpoints & responsive layouts |
| Hover, focus, checked, invalid, open, disabled | nesting: "&:hover", "&[data-open]", "&:has(…)" | Interactive state |
| A closed set of app states — idle, submitting, error | variants on styled | Interactive state |
| An open-ended value that changes while the page is live | a {props.X} prop token | Dynamic values |
| Screen size, but for one value rather than a block of styles | defineViewportClamp, or responsive tokens | Viewport clamp |
| Assistive tech and user settings | the same nesting, pointed at aria-* and prefers-* | Accessibility |
Bringing other things in
Four small APIs for the parts that aren't components — assets, motion, and the color math that tends to travel with them.
| I want to… | Reach for | Deep dive |
|---|---|---|
| Register a font from local files or a hosted stylesheet | defineFont | Fonts & imports |
| Pull in external CSS — a vendor reset, a print sheet, a syntax theme | defineImport | Fonts & imports |
| Describe multi-step motion | keyframes | Animations |
| Derive a shade from a color you already have | color() | Color function |
The project itself
| I want to… | Reach for | Deep dive |
|---|---|---|
| Configure tokens, templates, modifiers, strictness, output strategy | defineConfig in salty.config.ts | Configuration |
| Scaffold a file, force a build, bump every Salty package at once | the CLI: generate, build, up | Tooling |
Catch an unexported styled before it silently emits nothing | the ESLint plugin | Tooling |
Most of the type layer isn't written by anyone — your own definitions generate it. Add a color token and {colors.brand.main} starts autocompleting; name a media query and "@tabletDown" becomes a valid object key.
Where the imports live
Almost everything comes from @salty-css/core/* and behaves identically in every framework. styled is the exception, because a component factory has to hand back a component in your framework's own shape.
| Symbol | Import from |
|---|---|
styled | @salty-css/react/styled |
className | @salty-css/react/class-name |
defineVariables, defineTemplates, defineGlobalStyles, defineFont, defineImport | @salty-css/core/factories |
defineMediaQuery | @salty-css/react/config |
keyframes | @salty-css/react/keyframes |
color, defineViewportClamp | @salty-css/core/helpers |
defineRuntime | @salty-css/core/runtime |
defineConfig | @salty-css/core/config |
The @salty-css/react/* paths above are straight re-exports of the core ones, so if you're working without React — or writing something that has to run in both — import from core and nothing changes. Framework agnostic APIs covers exactly where that line falls, and what building without styled costs you.
When two of these could do the job
A handful of pairs come up often enough to be worth a tie-breaker.
styled or className? Use styled when you own the component — it folds the prop-to-class mapping, the attribute forwarding and the types into one call. Use className for markup you don't own, for DOM you're wiring up by hand, or anywhere JSX isn't in the picture. Same stylesheet either way.
Token, template, modifier, or helper? If you can name the thing, it's a token (one value) or a template (a bundle of properties). If it's a syntax — something with a variable part that would be a hundred names if you tried to enumerate them — it's a modifier. If it takes arguments and computes something, it's a helper. When you're torn, tokens and templates are the more discoverable pair, and you can always promote a pattern to a modifier once it's genuinely everywhere.
Variant, prop token, or runtime style? Climb only as far as the value forces you. A closed set of options is a variant. One unknown value in a known shape is a {props.X} token — one static rule, a CSS variable carrying the value. A style object that doesn't exist until the request runs is defineRuntime. Something changing every frame in the browser isn't any of these; it's a plain style prop.
Theming or runtime styles? Named looks you designed — light, dark, high contrast, a couple of brands — are theming, and they cost nothing at runtime. A look somebody else picked, out of a range you can't enumerate at build time, is runtime styles. The two aren't rivals; a product can easily need both.
What's not included
Worth knowing before you go looking for it.
There are no layout primitives and no component kit — no <Box>, no <Stack>, no pre-built modal. Salty gives you the styling layer and stays out of your component tree. There's a CSS reset available (reset in defineConfig), but the components on top of it are yours.
defineRuntime doesn't sanitize what you hand it, which matters the moment styles arrive from a public text field — Runtime styles has the section to read before you ship that. And there's no editor extension or language server to install: the types are generated from your own definitions on every build, which is why the ESLint plugin exists for the two silent failures TypeScript can't see.