
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@crescendolab/css-var-ts
Advanced tools
Type-safe, ergonomic utilities for authoring, registering, and consuming CSS Custom Properties (CSS Variables) in TypeScript.
.cssProps map you can spread into inline styles / style objects@emotion/css, @emotion/react (css prop), @mui/system (sx prop)getValue → var(--token))cssVarUtils.create@property at‑rule registrationpnpm add @crescendolab/css-var-ts
# or
npm i @crescendolab/css-var-ts
# or
yarn add @crescendolab/css-var-ts
import { cssVarUtils } from "@crescendolab/css-var-ts";
// 1. Define a base palette
const palette = cssVarUtils.define({
primaryBlue: "#0074D9",
accentPink: "#F012BE",
neutralBg: "#FFFFFF",
neutralFg: "#111111",
});
// 2. Define semantic tokens referencing the palette (type‑safe)
const semantic = cssVarUtils.define({
brand: palette.getValue("primaryBlue"),
text: palette.getValue("neutralFg"),
background: palette.getValue("neutralBg"),
});
// 3. Use in styles
const style: React.CSSProperties = {
// ...palette.cssProps, // Optional: variables have fallback values via `getValue`
// ...semantic.cssProps,
color: semantic.getValue("text"),
backgroundColor: semantic.getValue("background"),
};
Resulting (example) generated variable keys (random 8‑char suffix) look like:
--primaryblue-a1b2c3d4
--accentpink-9fe012ab
import { cssVarUtils } from "@crescendolab/css-var-ts";
// Base palette
const paletteDefinition = cssVarUtils.define({
navy: "#001F3F",
blue: "#0074D9",
aqua: "#7FDBFF",
black: "#111111",
});
// Semantic tokens referencing base palette
const semanticDefinition = cssVarUtils.define({
primary: paletteDefinition.getValue("navy"),
foreground: paletteDefinition.getValue("black"),
});
// Override one semantic var dynamically
const dynamicStyle = {
...paletteDefinition.cssProps,
...semanticDefinition.cssProps,
[semanticDefinition.getKey("primary")]: paletteDefinition.getValue("blue"),
color: semanticDefinition.getValue("foreground"),
};
@emotion/css)import { css } from "@emotion/css";
import {
gruvboxCssVarBaseDefinition,
gruvboxCssVarLightDefinition,
} from "./styles";
const container = css({
...gruvboxCssVarBaseDefinition.cssProps,
...gruvboxCssVarLightDefinition.cssProps,
color: gruvboxCssVarLightDefinition.getValue("fg"),
});
css prop)import { css } from "@emotion/react";
const button = css({
color: gruvboxCssVarLightDefinition.getValue("fg"),
backgroundColor: gruvboxCssVarLightDefinition.getValue("bg"),
});
sx prop)<Box
sx={{
...gruvboxCssVarBaseDefinition.cssProps,
...gruvboxCssVarLightDefinition.cssProps,
color: gruvboxCssVarLightDefinition.getValue("fg"),
}}
/>
See live Storybook demos below for full examples including light/dark variants and status colors.
Use createCssVarUtils to fully control how variable names are produced (e.g. ephemeral / randomized keys).
import { cssVarUtils, randomString, slugify } from "@crescendolab/css-var-ts";
const myCssVarUtils = cssVarUtils.create({
recordKeyToCssVarKey: (key) =>
`--my-${slugify(key)}-${randomString(8)}` as const,
});
const myDefinition = myCssVarUtils.define({
primary: "#0074D9",
});
myDefinition.getKey("primary"); // different each load
If you prefer fully readable, deterministic variable names (no random suffix) you can supply a static strategy. Be sure to manually ensure uniqueness across packages / bundles when using this approach.
import { cssVarUtils, slugify } from "@crescendolab/css-var-ts";
const staticCssVarUtils = cssVarUtils.create({
recordKeyToCssVarKey: (key) => `--static-${slugify(key)}` as const,
});
const staticDefinition = staticCssVarUtils.define({
primary: "#0074D9",
accent: "#F012BE",
});
staticDefinition.getKey("primary"); // "--static-primary"
staticDefinition.getValue("primary"); // "var(--static-primary, #0074D9)"
@property RegistrationYou can register variables with the CSS Typed OM for transitions, inheritance, etc.
const definition = cssVarUtils.define({ primaryColor: "#F012BE" });
CSS.registerProperty({
name: definition.getKey("primaryColor"),
syntax: "<color>",
inherits: true,
initialValue: "#F012BE",
});
For large-scale web applications (mono-repos, micro frontends, dynamic plugin architectures) you should take extra precautions to avoid accidental variable name collisions and to harden your design system surface.
Strengthen uniqueness: Provide a custom recordKeyToCssVarKey that injects a namespace (package name) plus a short random suffix. (You can optionally add build / commit info if desired.)
import {
cssVarUtils,
randomString,
slugify,
} from "@crescendolab/css-var-ts";
const namespace = process.env.APP_NAMESPACE ?? "app"; // e.g. marketing, analytics
const scopedCssVarUtils = cssVarUtils.create({
recordKeyToCssVarKey: (key) =>
`--${namespace}-${slugify(key)}-${randomString(8)}` as const,
});
For deterministic builds replace randomString(8) with a stable hash (e.g. of namespace + key).
Strongly recommended: Register core design tokens via @property to enforce syntax (e.g. <color>, <length>) and enable smoother transitions & validation.
Expose only semantic tokens to feature teams; keep raw palette tokens private to your design system package.
Document namespace conventions so new packages follow the same pattern.
Periodically audit generated variable names (e.g. collect with a build script) to detect drift or duplication.
These measures reduce the chance of silent styling regressions when independently deployed bundles are combined at runtime.
cssVarUtilsThe default exported utility bundle.
const definition = cssVarUtils.define({ accent: "#F012BE" });
definition.raw; // [{ accent: "#F012BE" }]
// example suffix will differ each run (8 random hex chars):
definition.cssProps; // { "--accent-a1b2c3d4": "#F012BE" }
definition.getKey("accent"); // "--accent-a1b2c3d4"
definition.getValue("accent"); // "var(--accent-a1b2c3d4, #F012BE)"
Each call to define() returns an object:
| Key | Type | Description |
|---|---|---|
raw | [base, ...exts] | Array of raw token records (base + extensions) |
cssProps | Record<cssVarKey, string> | Object you can spread into style systems to declare variables |
getKey(name) | string | Generated CSS variable name (e.g. --accent-…) |
getValue(name) | var(--token, val) | Proper var() usage string |
cssVarUtils.create(options)Low‑level factory to customize naming.
const custom = cssVarUtils.create({
recordKeyToCssVarKey: (k) => `--my-${k}` as const,
});
| Export | Purpose |
|---|---|
slugify | Deterministic slug for record keys |
randomString | Cryptographically strong random id (hex) for custom strategies |
| Category | Story | Code | Live Demo |
|---|---|---|---|
| Basic: Simple | Palette only | 01_basic/01_simple | Demo |
| Basic: Extend | .extend() usage | 01_basic/02_extend | Demo |
| Basic: Reset | Nested reset | 01_basic/03_reset | Demo |
| Emotion (class) | @emotion/css | 02_integration/01_emotion/01_emotion_css | Demo |
| Emotion (css prop) | @emotion/react | 02_integration/01_emotion/02_css_prop | Demo |
| MUI | sx prop | 02_integration/02_mui_sx_prop | Demo |
| Advanced | Static custom keys | 03_advanced/01_staticCssVarKey | Demo |
| Advanced | @property | 03_advanced/02_@property_atRule | Demo |
Adding a short random suffix mitigates accidental collisions when multiple packages / microfrontends define the same token names. It keeps names mostly human readable while providing lightweight namespacing. For fully deterministic readable names use a static strategy; for strict isolation include a package or build id.
List of approaches:
cssVarUtils): Slug + random 8‑char id = collision‑resistant and readable.--static-${slug} for fully readable tokens; ensure uniqueness manually.cssVarUtils.create + randomString / build hash for experiments, multi‑tenant isolation, A/B variants.Library surface is pure & easily unit testable (see randomString.test.ts for an example). Add tests as you add helpers: focus on stability of generated keys and referential integrity between getKey and getValue.
This repo uses changesets + GitHub Actions. On merge to main, a version PR is created / updated. Approve & merge to publish.
Ensure org settings allow the workflow to create & approve PRs: Settings → Code and automation → Actions → General → Workflow permissions:
PRs welcome! See the contributing guide.
Suggested areas:
Copyright (c) 2025 Crescendo Lab
Made with ❤️ to make CSS variables first-class citizens in TypeScript.
FAQs
Type-safe utilities for CSS Custom Properties
We found that @crescendolab/css-var-ts demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.