# 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**: [](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 `