# Salty CSS > Build-time CSS-in-TS for React, Next.js and Astro. Compiles `styled(...)` calls > in `*.css.ts` files to plain CSS at build time — zero runtime cost, with full > TypeScript autocomplete on design tokens, themes, variants and media queries. > Works with React Server Components. This file contains the full rendered documentation for Salty CSS, concatenated for LLM ingestion. Three framework variants (React, Next.js, Astro) are included in order — they are translations of the same docs with framework-specific install commands, imports, and code snippets. Source: https://salty-css.dev/llms-full.txt — Generated: 2026-05-24 Companion index: https://salty-css.dev/llms.txt --- # Documentation — React Source: https://salty-css.dev/docs/react/ Salty CSS is a build-time CSS-in-**TS** library for React, Next.js and Astro. You author styles in `.css.ts` files, the compiler turns them into real CSS, and your runtime ships with no styling engine attached. Meow. It's a good fit when you want **typed styles, real design tokens, and theming that doesn't need a React provider** — without giving up on the developer experience of writing CSS next to your component. ### What you get - **Typed styles** — `styled("button", { ... })` returns a typed component; variants become typed props. - **Design tokens** — `defineVariables` for static, **responsive**, and **conditional** tokens (the conditional ones are how theming works). - **Theming without a provider** — flip a `data-theme` attribute on `` and every consumer of `{theme.color}` updates. - **Templates** — `defineTemplates` for reusable style bundles with their own variants. - **Media queries that read like English** — `media.minWidth(720).and.dark`. - **Fluid sizing** — `defineViewportClamp` for `clamp()`-based values that scale with the viewport. - **Type-safe class names** — `className({ ... })` when you want the styles without the component wrapper. - **A CLI** — `npx salty-css init` / `generate` / `build` / `up` for the boring bits. ### TL;DR — install it Inside any React, Next.js or Astro project: ```bash npx salty-css init ``` Then create a component in a `*.css.ts` file: ```ts // /components/my-button.css.ts import { styled } from "@salty-css/react/styled"; export const Button = styled("button", { base: { padding: "0.75rem 1.25rem", borderRadius: "6px", background: "{theme.color}", color: "{theme.background}", }, }); ``` …and use it like any other . ### Where to go next - **New here?** → [Quick Start](/docs/quick-start/) walks you from `init` to a themed component with variants in about 15 minutes. - **Adding it to a project?** → [Installation](/docs/installation/) covers the per-framework wiring. - **Need the reference?** → [`styled`](/docs/api/styled/), [`className`](/docs/api/classname/), [`defineConfig`](/docs/api/config/). ### Search the docs Hit `Ctrl + K` (or `⌘ + K` on macOS) anywhere in the docs to open the search modal — or click **Search** at the top of the sidebar. Matching is fuzzy: partial terms like `variant`, `media` or `clamp` are usually enough. ### What's in the docs #### Getting Started - [Quick Start](/docs/quick-start/) — install, your first component, variants, theming, fonts. - [Installation](/docs/installation/) — per-framework setup (Next.js, Vite, Webpack, Astro). - [Usage](/docs/usage/) — file suffixes, dev-time naming, DevTools. - [Troubleshooting](/docs/troubleshooting/) — the fix list for the most common issues. - [CLI](/docs/cli/) — use Salty CSS from the command line. - [FAQ](/docs/faq/) — short answers to the things people ask first. #### Styling - [Component styles](/docs/basics/) — `styled`, `className`, and where each one fits. - [Variables](/docs/variables/) — design tokens with `defineVariables` (static, responsive, conditional). - [Theming](/docs/theming/) — dark mode and multi-theme without a provider. - [Fonts](/docs/fonts/) — `defineFont` for local files and remote stylesheets. - [Imports](/docs/imports/) — pull in external CSS with `defineImport`. - [Class styles](/docs/classnames/) — `className` for plain class-based styles. - [Variants](/docs/variants/) — prop-driven variants, compound, and `anyOfVariants`. - [Overrides](/docs/overrides/) — extend components, swap elements, forward props with `passProps`. - [Media queries](/docs/media-queries/) — media queries, container queries, breakpoints. - [Animations](/docs/animations/) — keyframes, stagger, pause/resume. - [Templates](/docs/templates/) — reusable styles with `defineTemplates`. #### Utilities - [Viewport clamp](/docs/viewport-clamp/) — fluid responsive sizing. - [Color function](/docs/color-function/) — manipulate colors at build time. - [Modifiers](/docs/modifiers/) — custom value transformers. #### API reference - [`styled` function](/docs/api/styled/) — full API for the `styled` component factory. - [`className` function](/docs/api/classname/) — full API for class-based styles. - [`defineConfig`](/docs/api/config/) — every option that lives in `salty.config.ts`. - [`define*` factories index](/docs/api/define-factories/) — one-page index of every factory. ### Get support Join the [Salty CSS Discord server](https://discord.gg/R6kr4KxMhP) for help, or check the [GitHub repository](https://github.com/margarita-form/salty-css) for source and issues. --- # Getting Started — React Source: https://salty-css.dev/docs/react/getting-started/ Everything you need to get a Salty CSS project off the ground. Start with the **Quick Start** for a fifteen-minute end-to-end walkthrough, or jump into **Installation** if you'd rather wire the build plugin into an existing app. The remaining pages cover day-to-day **Usage**, the **CLI**, **ESLint** support, common **Troubleshooting**, and **FAQ** answers. --- # Quick Start — React Source: https://salty-css.dev/docs/react/quick-start/ Goal: by the end of this page you have Salty CSS installed, a typed component on screen, prop-driven variants, dark mode that flips without a provider, and a custom font registered. Budget about 15 minutes. ### 1. Install Inside any React, Next.js, Vite, or Astro project, run: ```bash npx salty-css init ``` The CLI detects your framework, adds the right plugin (`@salty-css/next`, `@salty-css/vite`, `@salty-css/webpack`, or `@salty-css/astro`), drops a `salty.config.ts` in the project root, and wires up the build. If you'd rather wire it up yourself, see [Installation](/docs/installation/). ### 2. Your first component All Salty definitions live in files matching `*.css.ts`, `*.css.tsx`, `*.salty.ts`, `*.styled.ts`, or `*.styles.ts` — the suffix is how the compiler picks them up at build time. ```ts // /components/card.css.ts import { styled } from "@salty-css/react/styled"; export const Card = styled("section", { base: { padding: "1.5rem", borderRadius: "12px", background: "{theme.background}", color: "{theme.color}", boxShadow: "0 1px 2px rgba(0, 0, 0, 0.06)", }, }); ``` Use it like any other : ```tsx import { Component } from "./my-component.css"; const MyPage = () => { return ( This is a Salty CSS component ); }; export default MyPage; ``` Note the `{theme.background}` / `{theme.color}` tokens — we'll define those in step 4. ### 3. Add variants Variants turn props into typed style branches. Add a `size` axis and a `tone` axis: ```ts // /components/card.css.ts import { styled } from "@salty-css/react/styled"; export const Card = styled("section", { defaultVariants: { size: "medium", tone: "neutral" }, base: { padding: "1.5rem", borderRadius: "12px", background: "{theme.background}", color: "{theme.color}", }, variants: { size: { small: { padding: "1rem", borderRadius: "8px" }, medium: { padding: "1.5rem" }, large: { padding: "2.5rem", borderRadius: "16px" }, }, tone: { neutral: {}, brand: { background: "{theme.highlight}", color: "{theme.background}" }, muted: { background: "{theme.altBackground}" }, }, }, compoundVariants: [ { size: "large", tone: "brand", css: { fontWeight: 600 } }, ], }); ``` `size` and `tone` are now typed props. Render with: ```tsx import { Button } from "./button/button.css"; export const MyComponent = () => { return (
); }; ``` For more on variants — including `anyOfVariants` for "any of these branches matches" rules — see [Variants](/docs/variants/). ### 4. Add design tokens and dark mode Salty themes are CSS variables under the hood. You declare two (or more) value sets under `conditional.theme`, and the active set is picked by an attribute on an ancestor — usually ``. No provider, no context. ```ts // /styles/themes.css.ts import { defineVariables } from "@salty-css/core/factories"; export const palette = defineVariables({ colors: { ink: "#0d1117", paper: "#ffffff", sand: "#f5f1e8", coral: "#ff6b5a", }, }); export const themes = defineVariables({ conditional: { theme: { light: { background: "{colors.paper}", altBackground: "{colors.sand}", color: "{colors.ink}", highlight: "{colors.coral}", }, dark: { background: "{colors.ink}", altBackground: "#1c2128", color: "{colors.paper}", highlight: "{colors.coral}", }, }, }, }); ``` Activate a theme by setting the attribute on any ancestor — usually the root: ```html ... ``` That's the whole switch. Every `{theme.background}` / `{theme.color}` reference in your styles now reads the dark values. Flip the attribute back to `light` and it all updates again. For a working toggle that persists the choice in `localStorage` and avoids the first-paint flash: ```tsx // /components/theme-toggle.tsx "use client"; import { useEffect, useState } from "react"; type Theme = "dark" | "light"; const STORAGE_KEY = "theme"; const readInitial = (): Theme => { if (typeof window === "undefined") return "light"; const saved = window.localStorage.getItem(STORAGE_KEY) as Theme | null; if (saved === "dark" || saved === "light") return saved; return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; }; export const ThemeToggle = () => { const [theme, setTheme] = useState("light"); useEffect(() => { const initial = readInitial(); setTheme(initial); document.documentElement.dataset.theme = initial; }, []); const toggle = () => { const next: Theme = theme === "dark" ? "light" : "dark"; setTheme(next); document.documentElement.dataset.theme = next; window.localStorage.setItem(STORAGE_KEY, next); }; return ( ); }; ``` To avoid a flash of the wrong theme on first paint in Next.js, inline a tiny pre-hydration script in `app/layout.tsx` (or `_document.tsx` for the Pages Router) so the attribute is set before React hydrates: ```tsx // /app/layout.tsx const setThemeScript = ` try { var t = localStorage.getItem("theme"); if (t !== "dark" && t !== "light") { t = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } document.documentElement.dataset.theme = t; } catch (e) {} `; export default function RootLayout({ children }) { return ( ``` The `is:inline` directive ensures the script runs before the first paint, so users never see a flash of the wrong theme. See [Theming](/docs/theming/) for the deeper guide (system preference, multiple independent groups, deriving shades with `color()`). ### 5. Register a custom font `defineFont` registers a font and gives you back something you can use as a `font-family` value, a CSS variable, a class name, or a `style` spread. ```ts // /styles/fonts.css.ts import { defineFont } from "@salty-css/core/factories"; export const display = defineFont({ name: "Mona Sans", fallback: "system-ui, -apple-system, sans-serif", variants: [ { src: "/fonts/Mona-Sans.woff2", weight: "200 900", style: "normal", display: "swap", }, ], }); ``` Use it in a styled component: ```ts import { styled } from "@salty-css/astro/styled"; import { display } from "../styles/fonts.css"; export const Title = styled("h1", { base: { fontFamily: display, fontSize: "clamp(2rem, 4vw, 3.5rem)", color: "{theme.color}", }, }); ``` `display` stringifies to its `font-family` value, so it works inline. You can also read `display.variable` (the CSS custom property), `display.className`, or `display.style` (an object you can spread onto a `style` prop). See [Fonts](/docs/fonts/) for remote fonts (`import`) and per-variant overrides. ### What you have now A typed component with variants, a theme that flips on a single attribute, and a custom font registered through `defineFont`. That's the everyday Salty CSS surface. ### Where to go next - **Variants in depth** → [Variants](/docs/variants/) (compound, `anyOfVariants`, defaults). - **Reusable style bundles** → [Templates](/docs/templates/). - **Fluid type / spacing** → [Viewport clamp](/docs/viewport-clamp/). - **Media queries and breakpoints** → [Media queries](/docs/media-queries/). - **Full reference** → [`styled`](/docs/api/styled/), [`className`](/docs/api/classname/), [`defineConfig`](/docs/api/config/). ### Use the CLI - Scaffold a component: `npx salty-css generate [filePath]` - Force a CSS build: `npx salty-css build [directory]` - Bump packages: `npx salty-css up` ### Good to know 1. All Salty CSS definitions (`styled`, `className`, `keyframes`, `defineFont`, etc.) must live in `*.css.ts` / `*.css.tsx` (or `.salty.ts`, `.styled.ts`, `.styles.ts`). The suffix is the contract — that's how the compiler knows to evaluate the file. 2. `styled` can extend non-Salty components (`styled(ThirdPartyLink, { ... })`), but the wrapped component must accept a `className` prop. See [Overrides](/docs/overrides/). 3. CSS-in-JS values can be `string`, `number`, **function**, or **promise** — but importing heavy runtime libraries into a `*.css.ts` file will slow your build (and may crash if the library assumes a browser). Keep these files style-focused. ### If something didn't work → [Troubleshooting](/docs/troubleshooting/). The most common cause (a wrong filename suffix) is the very first entry. ### Get support Stuck? Drop into the [Salty CSS Discord](https://discord.gg/R6kr4KxMhP) and someone will untangle it with you. --- # Installation — Astro Source: https://salty-css.dev/docs/astro/installation/ Fastest way to get started with any framework is: ```bash npx salty-css init ``` The init command detects your framework, installs the right packages, creates `salty.config.ts`, and wires the build plugin into your existing bundler config. For most projects that's all you need. For a manual install on **Astro**, run: ```bash npm i @salty-css/astro ``` ### Astro 1. In your existing Astro repository you can run `npx salty-css init` to automatically configure Salty CSS. 2. Create your first Salty CSS component with `npx salty-css generate [filePath]` (e.g. `src/custom-wrapper`). 3. Import your component in any `.astro` page or layout and see it working! #### Manual configuration 1. Install the Astro integration: ```bash npm i @salty-css/astro ``` 2. Create `salty.config.ts` in your project root. 3. Add the Salty CSS integration to `astro.config.mjs`: ```ts import { defineConfig } from "astro/config"; import saltyIntegration from "@salty-css/astro/integration"; export default defineConfig({ integrations: [saltyIntegration()], }); ``` `saltyIntegration` accepts `{ srcDir?: string; rootDir?: string }`. `srcDir` defaults to `'src'`; `rootDir` defaults to the Astro config root. 4. Make sure that `salty.config.ts` and `astro.config.mjs` are in the same folder. 5. Build the `saltygen` directory by running your app once or via the CLI: `npx salty-css build [directory]`. 6. Import global styles from `saltygen/index.css` in your global stylesheet: `@import 'insert_path_to_index_css';`. ### Manual setup If `salty-css init` picks the wrong framework, can't find your bundler config, or you'd rather wire things up by hand, follow the manual setup for your stack. The framework-specific snippet above includes the precise commands and config edits; the steps below are the universal shape. 1. **Install the packages.** Each framework needs its runtime plus the core compiler: - Next.js: `npm i @salty-css/next @salty-css/core @salty-css/react` - React + Vite: `npm i @salty-css/vite @salty-css/core @salty-css/react` - React + Webpack: `npm i @salty-css/webpack @salty-css/core @salty-css/react` - Astro: `npm i @salty-css/astro @salty-css/core` 2. **Wire the bundler plugin.** `withSaltyCss(nextConfig)` for Next.js, `saltyPlugin(__dirname)` for Vite, `saltyPlugin(config, __dirname)` in `webpack.config.js` for Webpack, the integration for Astro. 3. **Create `salty.config.ts`** in the same directory as your bundler config (e.g. next to `next.config.ts` or `vite.config.ts`): ```ts import { defineConfig } from "@salty-css/astro/config"; export const config = defineConfig({ // Add variables, templates, modifiers as you grow. }); ``` 4. **Import the generated stylesheet.** With the default `importStrategy: 'root'`, Salty CSS expects one stylesheet to be imported at your app root. Most framework plugins do this for you on first run; if not, add `@import "../saltygen/index.css";` (or the appropriate path) to your global CSS. 5. **Build once.** Run your dev server (or `npx salty-css build`) so `saltygen/` exists before the first render. ### Peer dependencies You'll need: | Package | Version | Notes | | --------------- | -------------------- | ---------------------------------------------------------------- | | `node` | 18 or newer | Required for the build pipeline (esbuild + ESM). | | `typescript` | 5.x | Needed because `.css.ts` files are evaluated through TypeScript. | | `react` | 18 or 19 (when using React/Next) | The `@salty-css/react` runtime targets modern React. | | `next` | 13 (App Router) or newer | For `@salty-css/next`. | | `vite` | 5 or newer | For `@salty-css/vite`. | | `astro` | 4 or newer | For `@salty-css/astro`. | The exact ranges live in each package's `peerDependencies` — check `package.json` if you're on a fringe version. ### Verify your install After running the dev server (or `npx salty-css build`), confirm: 1. **`saltygen/` exists** at the root of the package you initialised. It should contain at least `index.css` and a `salty.config.js` snapshot. 2. **`saltygen/index.css` is non-empty.** A few `@layer` declarations and your reset should be there even before you write any components. 3. **A test component renders styled.** Create a `*.css.ts` file with a tiny styled component, use it on a page, and inspect the element in DevTools — you should see a class like `s_xxxx` and a matching rule in the Styles panel. 4. **No build warnings about missing plugin.** Salty CSS logs a warning at build time if the plugin didn't load — search your terminal output for `salty-css`. If any step fails, jump to [Troubleshooting](/docs/troubleshooting/). Want the linter to catch missing `export`s and misplaced `variants` before they reach the compiler? See [ESLint setup](/docs/eslint/). --- # Usage — Astro Source: https://salty-css.dev/docs/astro/usage/ This guide covers the basic usage of Salty CSS components and features across different frameworks. ### Create components Salty CSS only picks up files whose names end with one of these suffixes: | Suffix | When to use it | | ------------ | -------------------------------------------------------------------- | | `.css.ts` | Default for any style or component file. Works everywhere. | | `.css.tsx` | Same as `.css.ts`, but JSX is allowed in the file. | | `.salty.ts` | Alias of `.css.ts` — pick whichever reads better in your project. | | `.styled.ts` | Alias of `.css.ts`, conventionally used for `styled` factories. | | `.styles.ts` | Alias of `.css.ts`, conventionally used for `defineTemplates` etc. | A `.ts` file with the same content but missing the right suffix will type-check fine but produce **no CSS** at build time — this is the single most common "my styles aren't appearing" cause. See [Troubleshooting](/docs/troubleshooting/) if you hit it. ### Basic Component Structure ```ts // /components/my-component.css.ts import { styled } from "@salty-css/astro/styled"; export const Component = styled("div", { className: "wrapper", // Optional custom class name element: "section", // Optional override for the rendered HTML element base: { // Base styles that are always applied display: "flex", padding: "1rem", backgroundColor: "#f5f5f5", }, variants: { // Conditional styles based on props size: { small: { padding: "0.5rem" }, large: { padding: "2rem" }, }, color: { primary: { backgroundColor: "blue", color: "white" }, secondary: { backgroundColor: "gray", color: "black" }, }, }, compoundVariants: [ // Styles applied when multiple variant conditions are met { size: "small", color: "primary", css: { borderRadius: "4px" }, }, ], }); ``` ### Using Components ```astro --- // src/pages/index.astro import { Component } from "../components/my-component.css"; --- This is a Salty CSS component ``` ### Naming components in DevTools In development builds, every styled component renders with a `data-component-name` attribute matching its export name. Search for `[data-component-name="Button"]` in the elements panel to jump straight to it. You can override the label with the `displayName` option on the styled definition: ```ts export const PrimaryButton = styled("button", { displayName: "PrimaryButton", base: { /* … */ }, }); ``` In production builds the attribute is stripped, so it's a debugging aid only. ### Where to go next - **Add prop-driven styles** → [Variants](/docs/variants/) (and [`anyOfVariants`](/docs/variants/#anyof-variants---or-logic) for OR-logic). - **Share style bundles across components** → [Templates](/docs/templates/). - **Add design tokens** → [Variables](/docs/variables/). - **Add dark mode** → [Theming](/docs/theming/). - **Extend a third-party component** → [Overrides](/docs/overrides/). - **API reference** → [`styled`](/docs/api/styled/) · [`className`](/docs/api/classname/) · [`defineConfig`](/docs/api/config/). ### Demo Projects - **Next.js Demo Project**: [View on GitHub](https://github.com/margarita-form/salty-css-website) - **React + Vite Demo**: [View on GitHub](https://github.com/margarita-form/salty-css-react-vite-demo) - **CodeSandbox Demo**: [![Edit margarita-form/salty-css-react-vite-demo/main](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/github/margarita-form/salty-css-react-vite-demo/main?import=true&embed=1) --- # Troubleshooting — Astro Source: https://salty-css.dev/docs/astro/troubleshooting/ A short, opinionated checklist for the issues we see most often. If your symptom isn't here, the [Discord server](https://discord.gg/R6kr4KxMhP) is the fastest way to get unstuck. ### My styles aren't appearing Work through these in order — the first one is the cause about 80% of the time. 1. **Wrong filename suffix.** Salty CSS only picks up `*.css.ts`, `*.css.tsx`, `*.salty.ts`, `*.styled.ts`, or `*.styles.ts`. A file named `my-component.ts` will type-check fine but produce no CSS. Rename it. 2. **`saltygen/index.css` is stale or missing.** Restart the dev server. If you're not using a dev server, run `npx salty-css build` to regenerate it. 3. **Plugin not wired up.** Confirm `withSaltyCss` (Next.js), `saltyPlugin` (Vite), or the Astro integration appears in your bundler config. Without the plugin, the compiler never runs. 4. **Importing `saltygen/index.css` is missed.** With `importStrategy: 'root'` (the default), the generated stylesheet must be imported once at the root of your app. Frameworks like Next.js do this for you; check the install snippet for your stack if you set things up manually. 5. **The component isn't actually used.** Salty CSS tree-shakes unused exports — if no module imports the styled component, no CSS is emitted for it. Make sure the export reaches a rendered tree. ### I can see the class but the rule isn't applying - **Specificity conflict.** A higher-specificity rule from another stylesheet may be winning. Inspect the element in DevTools and look at the cascade. Bump the Salty rule's `priority` (see [`styled` API](/docs/api/styled/)) or scope your override more tightly. - **Order of cascade layers.** Salty CSS uses `@layer` internally. Anything you import with [`defineImport`](/docs/imports/) lives in the earliest layer, so it always loses to your own styles — and vice versa, your imports won't override Salty rules. - **`anyOfVariants` losing to a regular variant.** `anyOfVariants` uses `:where()` and has zero specificity by design. If you need it to win, move the rule into a regular variant or a `compoundVariants` entry. ### A token reference shows up literally as `{colors.brand}` in the browser That's the parser refusing to resolve an unknown path: - Typo? Check `defineVariables({ colors: { ... } })` matches the path exactly. - Did you forget to register the variables file? With `defineVariables` in a `*.css.ts` file, the file just needs to be imported somewhere in your build graph (typically you re-export it from a styles barrel). - Variables defined inside [`defineConfig`](/docs/api/config/) are picked up automatically; the file imports apply only to standalone `defineVariables` calls. Turn on `strict: true` (or `'warn'`) in `defineConfig` to surface these as build errors instead of silently passing them through. ### Build error: "Unknown frontmatter key" The website docs parser validates frontmatter against a fixed allowlist. If you added a custom key to a doc page, either remove it or extend [`docs-content.ts`](https://github.com/margarita-form/salty-css-website/blob/main/src/lib/docs-content.ts) to accept it. ### `npx salty-css init` picked the wrong framework Re-run with the framework set explicitly via the package manager — `init` looks at the dependencies it can see. If you're in a monorepo, run it from the package's own root, not the workspace root. If it already wrote the wrong config, deleting `salty.config.ts` and re-running is the cleanest fix. ### React Server Components / SSR - Salty CSS works in **server components** — styles are emitted at build time, so there's no runtime dependency to ship to the server. The `styled` function returns a regular React component you can render anywhere. - Where you do need `"use client"`: any component that uses runtime React features (state, effects, event handlers) — including a theme toggle — must be a client component, but the styled component it uses can stay in server land. - Hydration mismatches around theming are usually a flash-of-wrong-theme issue: the server renders one theme attribute, the client toggle script applies another. See the [Theming](/docs/theming/) page for the inline pre-hydration script pattern. ### Finding a generated class in DevTools Every styled component emits a `data-component-name` attribute in development. Open the element inspector, find the attribute (e.g. `data-component-name="Button"`), and the class name on that element is the Salty-generated hash. Searching the Styles panel for that hash takes you straight to the rule. ### Importing heavy modules in a `.css.ts` file Salty CSS evaluates `.css.ts` files at build time, so importing big runtime libraries (or anything that touches `window`) can slow down compilation or fail outright. Keep `.css.ts` files focused on style definitions; if you need a value from a heavy module, derive it once and re-export it from a lightweight intermediate file. For modules you genuinely need at build time but want excluded from the Salty bundle, add them to `externalModules` in [`defineConfig`](/docs/api/config/#externalmodules). ### Still stuck? - [Discord](https://discord.gg/R6kr4KxMhP) — community + maintainers. - [GitHub issues](https://github.com/margarita-form/salty-css/issues) — for confirmed bugs and feature requests. --- # Frequently Asked Questions — Astro Source: https://salty-css.dev/docs/astro/faq/ ### About Salty CSS #### What is Salty CSS? Salty CSS is a TypeScript-first, build-time CSS-in-TS library for React, Next.js and Astro. You author styles in `.css.ts` files using a typed `styled()` API, and the compiler turns them into real CSS at build time. The runtime ships with no styling engine — your components just render with the generated class names. #### Is Salty CSS zero-runtime? The styling layer is. Styles, variants, templates, and tokens are resolved by the build, so the only thing that ships at runtime is a small helper that maps your variant props to class names. There is no runtime style serializer, no in-DOM `` + an element with `className={className}` without further wiring. | | `getDynamicStylesCss` | alias of `css` | Deprecated. Kept exported for backward compatibility with the standalone helper that predates `defineRuntime`. Prefer `css`. | #### The `config` argument `config` is `Partial` — the same shape `defineConfig` accepts. The fields the runtime actually uses are `variables`, `templates`, `mediaQueries`, and `modifiers`. The practical recipe is to import your project's existing config and pass it in, so CMS-supplied `{colors.brand}` tokens resolve to the same CSS variables your `styled` components already use: ```ts import { defineRuntime } from "@salty-css/astro/runtime"; import config from "../../salty.config"; const runtime = defineRuntime(config); ``` `defineRuntime()` (with no argument) is fine when the incoming styles don't reference any tokens or templates. ### Feature support `defineRuntime` runs the same parser as the build-time compiler, so most Salty features come along for free. The exception is anything that requires a component wrapper — `defineRuntime` returns CSS, not a component. | Feature | Supported | Notes | | ------------------------------------------------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------- | | Token substitution (`{colors.brand}`) | yes | Pass the matching `variables` in `config`. | | Media queries (`@media (...)`) | yes | Scoped under the resolved class — `.${className} { ... }` inside the `@media` block. | | Modifiers / pseudo-classes (`&:hover`, `&[data-state]`) | yes | Ampersand-expansion is identical to the build-time parser. | | Nested selectors (`'& > svg'`) | yes | Combinators are appended to the scope class. | | Templates (`textStyle: 'caption'`) | yes | Pass the matching `templates` in `config`. | | `variants` / `compoundVariants` / `anyOfVariants` | partial | The parser emits the variant selectors (`.${className}.size-sm`, etc.), but there is **no prop → class mapping** here. Either pass a flat object, or select the active branch yourself before calling `resolve`. | | `defaultVariants`, `defaultProps`, `passProps`, `element`, `as` | no | These are `styled()` component concepts. `defineRuntime` returns strings. | | `keyframes`, `defineGlobalStyles`, `defineFont` | no | Build-time only — they need to live in the generated stylesheet, not a per-request `

{block.heading}

{block.body}

); } ``` The class is a hash of `block.css`, so two blocks with identical styles produce the same class — if you want to ship one rule per unique shape, collect the pairs across the request and dedupe by `className` before rendering the ` {children} ); } ``` `{colors.text.onBrand}` resolves against your project config; the `&:hover` and `@media` blocks compose exactly the way they do inside a `styled()` definition. ### Scoping `resolve(styles)` defaults the scope to `.${className}`, which is what you want most of the time — render the returned CSS in a `