icss-replace-symbols review
Our browser build of icss-replace-symbols 1.1.0 was 0.9 KB minified and 0.5 KB gzipped. The package does one old CSS Modules linking job: it walks a PostCSS tree and substitutes named tokens inside declaration values and @media parameters. It changes the tree in place, returns no CSS string, and does not rewrite selectors, property names, or other at-rules. Version 1.1.0 is still current; the original repository now redirects to icss-utils, where the maintained replaceSymbols function lives.
icss-replace-symbols 1.1.0 installed in 0.5 seconds, occupied 1 MB, and produced a 0.5 KB gzipped browser build in our sandbox, but its repository now redirects to icss-utils. Keep it for an old linker that already relies on this behavior; choose icss-utils for new code.
We installed it
| Install | ✓ · 0.5s | 1 package on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package |
| Browser | 0.5 KB | gzipped (0.9 KB minified), bundled with esbuild |
| Types | — | no TypeScript types found |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does icss-replace-symbols install cleanly?
Yes. In a fresh container with an empty cache, npm install icss-replace-symbols finished in 0.5s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.
How much does icss-replace-symbols add to a browser bundle?
0.5 KB gzipped (0.9 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does icss-replace-symbols work with both ESM and CommonJS?
Yes. Both import 'icss-replace-symbols' and require('icss-replace-symbols') worked in Node 22 in our run. The package is published as CommonJS.
Does icss-replace-symbols include TypeScript types?
No type declarations were found in our install, so TypeScript users need their own declarations.
icss-replace-symbols or icss-utils: which should you use?
icss-utils: Use it for new ICSS work that needs replaceSymbols plus import and export extraction. icss-replace-symbols 1.1.0 installed in 0.5 seconds, occupied 1 MB, and produced a 0.5 KB gzipped browser build in our sandbox, but its repository now redirects to icss-utils.
When should you not use icss-replace-symbols?
You are writing a new ICSS plugin: the old GitHub URL redirects to icss-utils, which exports replaceSymbols alongside import and export helpers
Use it if
- You maintain a CSS Modules or PostCSS integration that already imports icss-replace-symbols and depends on its exact token matcher
- You already have a PostCSS root and only need synchronous substitution in declaration values and @media parameters
- You need the historical behavior where dotted strings, hash values, dimensions, and custom-property-like names can be translation keys
- You are repairing an old build pipeline and changing the linker dependency would create more risk than keeping this 24 KB package
- You are writing a new ICSS plugin: the old GitHub URL redirects to icss-utils, which exports replaceSymbols alongside import and export helpers
- You expect a PostCSS plugin that can go straight into postcss([...]): this export accepts a root and translations object, so a processor needs a wrapper
- You need selector, property-name, @supports, or @container substitutions: the implementation visits declaration values and @media parameters only
- You need TypeScript declarations or an exports map: version 1.1.0 includes neither, even though both require() and ESM import worked in our sandbox
- You want a directly maintained dependency: 1.1.0 has no successor release, and the project repository was folded into icss-utils
Setup reality
Our fresh install of icss-replace-symbols 1.1.0 finished in 0.5 seconds. It left 1 package and 1 MB on disk, with 0 direct dependencies, 0 peer dependencies, and 0 audit findings. The package itself is 24 KB unpacked under the ISC license. There is no native build, credential, or configuration step.
The package does not parse CSS. Pass it a PostCSS root that supplies walkDecls and walkAtRules, plus a plain object whose keys are symbols and whose values are replacement strings. It mutates that root and returns undefined. When a processor owns the tree, call it from a PostCSS plugin hook instead of placing the function itself in the plugin array.
Version 1.1.0 is CommonJS without an exports map. Both require() and ESM import loaded on Node 22 in our sandbox, but CommonJS receives the Babel-shaped module and uses .default for the main function. No TypeScript declarations ship with the package, so typed projects need a local declaration or should move to icss-utils.
Matching is lexical rather than CSS-value-aware. The expression accepts word characters plus $, #, hyphen, and dot, so palette.red is one token and a mapping for red will not change its suffix. A replacement runs once at the matched position, not recursively. Falsy replacement values are ignored because the implementation tests the replacement before inserting it.
Patterns
Replace one value token replace-declaration-value
import postcss from 'postcss';
import replaceSymbols from 'icss-replace-symbols';
const root = postcss.parse('.button { color: brandColor }');
replaceSymbols(root, { brandColor: '#2563eb' });
console.log(root.toString());Version 1.1.0 mutates the PostCSS root and returns undefined.
Replace several tokens in one declaration replace-several-tokens
const root = postcss.parse('.card { box-shadow: offsetX offsetY blur shadowColor }');
replaceSymbols(root, {
offsetX: '0',
offsetY: '2px',
blur: '8px',
shadowColor: 'rgb(0 0 0 / 20%)',
});
console.log(root.toString());Each mapped token in the declaration value is handled during the same synchronous walk.
Expand an @media token replace-media-query
const root = postcss.parse('@media small { .grid { display: grid } }');
replaceSymbols(root, { small: '(max-width: 599px)' });
console.log(root.toString());Only @media parameters are visited; @supports and @container parameters stay unchanged.
Wrap the helper as a PostCSS 8 plugin wrap-postcss-eight
const translations = { gapToken: '1rem' };
const linker = {
postcssPlugin: 'local-icss-linker',
Once(root) {
replaceSymbols(root, translations);
},
};
const result = await postcss([linker]).process('.stack { gap: gapToken }', { from: undefined });The package export is a tree helper, not a PostCSS plugin object.
Load the Babel default export with require load-from-commonjs
const postcss = require('postcss');
const replaceSymbols = require('icss-replace-symbols').default;
const root = postcss.parse('.badge { background: accent }');
replaceSymbols(root, { accent: 'tomato' });The CommonJS entry exposes the main function on .default and replaceAll as a named property.
Confirm that only the value changes preserve-selector-and-property
const root = postcss.parse('.brand { brand: brand }');
replaceSymbols(root, { brand: 'renamed' });
console.log(root.toString());
// .brand { brand: renamed }The class selector and declaration property are outside the package's two tree walks.
Rename a custom property reference replace-custom-property-reference
const root = postcss.parse('.alert { color: var(--danger) }');
replaceSymbols(root, { '--danger': '--color-error' });
console.log(root.toString());This changes the reference in the value; a declaration named --danger would keep its property name.
Use hash and dimension strings as keys replace-css-literals
const root = postcss.parse('.box { border-color: #f00; margin: 0.5em }');
replaceSymbols(root, { '#f00': 'rebeccapurple', '0.5em': '8px' });
console.log(root.toString());The 1.1.0 matcher accepts #, $, hyphen, dot, and word characters.
Treat a dotted name as one symbol handle-dotted-symbols
const root = postcss.parse('.item { color: palette.red; background: red }');
replaceSymbols(root, { red: 'green' });
console.log(root.toString());
// .item { color: palette.red; background: green }A mapping for red does not replace the red suffix of palette.red because the dot belongs to the match.
Account for one-pass substitution avoid-recursive-substitution
const root = postcss.parse('.item { color: primary; background: secondary }');
replaceSymbols(root, { primary: 'secondary', secondary: 'black' });
console.log(root.toString());
// .item { color: secondary; background: black }Inserted text is not processed again at the same location.
Replace tokens in a standalone string call-replace-all
const { replaceAll } = require('icss-replace-symbols');
const value = replaceAll({ spacing: '12px', ink: '#111' }, '0 spacing ink');
console.log(value);
// 0 12px #111replaceAll is exported in 1.1.0 even though the README documents only the default tree helper.
Avoid empty-string replacements detect-falsy-replacement
const root = postcss.parse('.item { color: token }');
replaceSymbols(root, { token: '' });
console.log(root.toString());
// .item { color: token }The implementation checks replacement truthiness, so an empty string does not remove a token.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| icss-utils | npm | Use it for new ICSS work that needs replaceSymbols plus import and export extraction. |
| postcss-modules-values | npm | Use it when you want a PostCSS plugin for defining and importing values between CSS Modules files. |
| postcss-modules | npm | Use it when the job is a complete CSS Modules transformation rather than a low-level tree mutation. |
| postcss-custom-properties | npm | Use it when standard CSS custom properties describe the values you need to transform. |
More web frontend guides
postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

