Version 0.4.0 now released! See the release notes on GitHub Releases

→ 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:

/styles/system.css.ts
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:

/components/card.css.ts
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 as css-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 forDeep dive
Build a typed component whose variants are JSX propsstyledStyled api
Get a class string and put it on markup I already haveclassNameClass names
Style the document itself — a reset, body, bare <a>defineGlobalStyles, or global / reset in defineConfigGlobal styles
Style something whose values don't exist until the request runsdefineRuntimeRuntime styles
Reach into a component from another componentnesting and selectors — no new APIScoping 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 forDeep dive
A named value, shared and typed across the systemdefineVariables + {token.path}Variables & tokens
A whole set of values that swaps with contextdefineVariables conditional scope + a data-theme attributeTheming
A bundle of properties that always travel togetherdefineTemplatesTemplates
A value shape recognised anywhere you write itmodifiers, registered in salty.config.tsModifiers
A computation that turns arguments into a valuea helper — your own function, or a built-inHelpers

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 forDeep dive
Viewport width, print, prefers-reduced-motion, prefers-color-schemedefineMediaQuery + "@name", or a raw "@media (…)" keyBreakpoints & responsive layouts
The size of a parent box rather than the screencontainerType + a "@container (…)" keyBreakpoints & responsive layouts
Hover, focus, checked, invalid, open, disablednesting: "&:hover", "&[data-open]", "&:has(…)"Interactive state
A closed set of app states — idle, submitting, errorvariants on styledInteractive state
An open-ended value that changes while the page is livea {props.X} prop tokenDynamic values
Screen size, but for one value rather than a block of stylesdefineViewportClamp, or responsive tokensViewport clamp
Assistive tech and user settingsthe 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 forDeep dive
Register a font from local files or a hosted stylesheetdefineFontFonts & imports
Pull in external CSS — a vendor reset, a print sheet, a syntax themedefineImportFonts & imports
Describe multi-step motionkeyframesAnimations
Derive a shade from a color you already havecolor()Color function

The project itself

I want to…Reach forDeep dive
Configure tokens, templates, modifiers, strictness, output strategydefineConfig in salty.config.tsConfiguration
Scaffold a file, force a build, bump every Salty package at oncethe CLI: generate, build, upTooling
Catch an unexported styled before it silently emits nothingthe ESLint pluginTooling

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.

SymbolImport 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.