Version 0.1.0 just released! Check out the release notes from GitHub Releases

Compile a Team-Wide Pixel-to-Rem Scale Into Every React Style Object Through a Single Build-Time Modifier

Custom Modifier Scale in React

Design systems lose to drift: one engineer writes padding: "14px", another writes padding: "0.875rem", and the spacing scale a designer carefully built starts breaking apart. Salty CSS lets you build a proprietary DSL for your design system with defineConfig modifiers. A modifier is a regex pattern plus a transform function — when Salty CSS sees a matching value at build time, it rewrites it. The pattern is yours to design; the enforcement is automatic.

Register the modifier

// /salty.config.ts
import { defineConfig } from "@salty-css/core/config";

export const config = defineConfig({
  modifiers: {
    // Shorthand: write `space:3` and get `12px` (3 × 4px base unit).
    spaceShorthand: {
      pattern: /^space:(\d+)$/,
      transform: (match) => {
        const n = Number(match.replace("space:", ""));
        return { value: `${n * 4}px` };
      },
    },

    // Inject extra CSS alongside the rewritten value:
    elevation: {
      pattern: /^elevation:(\d+)$/,
      transform: (match) => {
        const level = Number(match.replace("elevation:", ""));
        return {
          value: `${level * 2}px ${level * 4}px ${level * 6}px rgba(0,0,0,0.12)`,
          css: { transform: "translateZ(0)" }, // emitted alongside
        };
      },
    },
  },
});

Use the shorthand at the call site:

styled("div", {
  base: {
    padding: "space:3",       // → 12px
    boxShadow: "elevation:2", // → 4px 8px 12px rgba(0,0,0,0.12)
  },
});

spaceShorthand matches strings like "space:3" and rewrites them to "12px". The transform receives the full matched string (not the capture groups) — re-parse it inside the function to extract the numeric portion.

Use the DSL in any React component

// /components/card.css.ts
import { styled } from "@salty-css/react/styled";

export const Card = styled("article", {
  base: {
    // Reads as "padding step 4 on the design scale" — the regex
    // ensures only valid scale steps compile, off-scale values fail loudly.
    padding: "space:4",
    gap: "space:2",
    borderRadius: "space:1",
  },
});

Why this beats the obvious alternatives

  • A padding variant for every scale step explodes the variant API and ties spacing to component identity.
  • Design tokens via defineVariables are great for values, but they don't surface as a fluent shorthand at the call site. A modifier is intentionally regex-driven, so unmatched values fall back to plain CSS without the developer having to think about it.
  • Code review as the enforcement layer is human, slow, and inconsistent. A modifier moves the check to build time.

What it produces

The matched string is replaced with the transform's return value before the parser sees it. After the build, padding: "space:4" is padding: 16px in saltygen/index.css — no trace of the DSL is left in the bundle. Off-scale values don't match the pattern and pass through unchanged; if you want hard enforcement you can throw inside the transform on illegal inputs, and the build will fail.

Gotchas

  • Anchor the pattern. /^space:(\d+)$/ is right; /space:(\d+)/ will match "space:3 extra" and break with surprising results. The ^…$ anchors keep the rewrite explicit.
  • Strings only. Modifiers run against string values. padding: 12 (numeric) is handled by defaultUnit on defineConfig, not by modifiers.
  • One scale, one source of truth. If your modifier is "multiply by 4", document that. Future readers won't reverse-engineer the curve from the transform.
  • Inject extra CSS sparingly. A modifier can return { value, css } to emit additional declarations next to the rewritten property (the snippet above includes an elevation example). It's powerful for shadows and gradients, but easy to abuse.

See also