defineVariables Tokens Compiled Into .astro Stylesheet Output — Responsive, Conditional, Theme-Aware
Design Tokens for .astro Pages
defineVariables is how you register design tokens — colors, spacing, font sizes, anything you want to reuse — so the rest of your styles can reference them with {token.path} syntax. Tokens become CSS custom properties on :root, so you keep the runtime cost of regular CSS variables while writing them in TypeScript with autocomplete and build-time validation.
It has three scopes:
| Scope | What it does | Use it for |
|---|---|---|
| Static (root) | Top-level keys land on :root once and never change. | Brand colors, fixed spacing, anything that doesn't depend on state. |
responsive | Sub-trees keyed by a defined media query — the same token name swaps value automatically when the media query matches. | Fluid type scales, breakpoint-sensitive spacing. |
conditional | Sub-trees keyed by a parent selector (e.g. data-theme="dark") — the value flips when an ancestor selector matches. | Theming and dark mode. See Theming. |
Static variables
Pass a plain object. Keys nest as deeply as you like; the path becomes the token reference.
// /styles/variables.css.ts import { defineVariables } from "@salty-css/core/factories"; export default defineVariables({ colors: { black: "#0a0a0a", white: "#f0f0f0", highlight: "aqua", brand: { main: "#0070f3", muted: "#6699cc", }, }, spacing: { small: "8px", medium: "16px", large: "32px", }, fontFamily: { heading: "var(--font-family-logo)", body: "var(--font-family-main, helvetica, sans-serif)", }, });
Reference tokens from anywhere a Salty style object accepts a value:
import { styled } from "@salty-css/astro/styled"; export const Card = styled("div", { base: { background: "{colors.brand.main}", color: "{colors.white}", padding: "{spacing.large}", fontFamily: "{fontFamily.body}", }, });
Token paths are validated at build time, so a typo like {colros.brand.main} surfaces in compiler output rather than as an unstyled element in the browser.
Responsive variables
Variables nested under responsive.base are the defaults. Adding a key like '@largeMobileDown' (the name of a media query you defined with defineMediaQuery) overrides any matching token name when that query matches.
// /styles/variables.css.ts import { defineVariables } from "@salty-css/core/factories"; import { HDClamp, MobileClamp } from "./helpers.css"; export default defineVariables({ responsive: { base: { spacing: { small: HDClamp(8), medium: HDClamp(20), large: HDClamp(36), pageMargin: HDClamp(120), }, fontSize: { headline: { small: HDClamp(24), regular: HDClamp(36), large: HDClamp(64), }, body: { regular: HDClamp(16), large: HDClamp(24), }, }, }, "@largeMobileDown": { spacing: { pageMargin: MobileClamp(30), }, fontSize: { headline: { small: MobileClamp(24), regular: MobileClamp(32), large: MobileClamp(42), }, }, }, }, });
Consume the tokens the same way you would static ones — the browser swaps the value when the breakpoint matches.
styled("h1", { base: { fontSize: "{fontSize.headline.large}", // 64 → 42 below 900px paddingInline: "{spacing.pageMargin}", // 120 → 30 below 900px }, });
You only need to redeclare the tokens that actually change at the breakpoint. Anything you leave out keeps its base value.
Conditional variables
conditional lets you swap tokens based on an ancestor selector — typically a data-theme attribute or a class name. The structure is conditional[group][value]: { ...tokens }. Salty CSS emits rules like [data-theme="dark"] { ... } so you can flip the whole theme by toggling one attribute.
// /styles/themes.css.ts import { defineVariables } from "@salty-css/core/factories"; export const themes = defineVariables({ conditional: { theme: { dark: { background: "{colors.black}", color: "{colors.white}", }, light: { background: "{colors.white}", color: "{colors.black}", }, }, }, });
Activate a theme by setting the corresponding attribute on an ancestor:
<html data-theme="dark"> ... </html>
Then use the tokens like any other:
styled("section", { base: { background: "{theme.background}", color: "{theme.color}", }, });
For a full dark-mode walkthrough (toggle wiring, prefers-color-scheme integration, derived shades) see Theming. That page also covers when to use conditional vs responsive — the short version: conditional is for user- or markup-driven switches; responsive is for environment-driven ones like OS dark mode.
Mixing all three scopes
Static, responsive, and conditional variables can coexist in one file (or be split across many — they all merge into the same :root namespace at build time):
defineVariables({ // Static colors: { brand: { main: "#0070f3" } }, // Responsive responsive: { base: { spacing: { gutter: "32px" } }, "@largeMobileDown": { spacing: { gutter: "16px" } }, }, // Conditional conditional: { theme: { dark: { surface: "#111" }, light: { surface: "#fafafa" }, }, }, });
Inspecting generated variables in DevTools
Every token becomes a CSS custom property on :root (or on the conditional selector). The property name is derived from the token path with dashes — colors.brand.main becomes --colors-brand-main, spacing.pageMargin becomes --spacing-page-margin, and so on. Inspect :root in DevTools to see all of them at once, or use Computed → Show all to confirm what a specific element resolves to.
Best practices
Keep the nesting shallow — two to three levels is comfortable; anything deeper tends to read as noise at the call site. Name tokens by purpose, not raw value: spacing.pageMargin ages better than spacing.px-120, because the value can change without touching every call site. Keep conditional groups orthogonal: a theme group and a density group toggled separately compose freely; a group trying to track both concerns at once gets messy fast. And reach for responsive when a value should swap at a breakpoint — for fluid scaling without a hard step, Viewport clamp is the right tool.
See also
- Theming — dark-mode patterns built on
conditional. - Media queries — define the names used as
responsivekeys. - Viewport clamp — fluid sizing without breakpoints.
defineConfigreference — wire variables into your Salty config.