css
css is the old Rework CSS parser and stringifier. It turns a stylesheet string into a plain JavaScript AST with rules, declarations, comments, selected at-rules, source positions, and non-enumerable parent links; you can edit that tree and turn it back into readable or compressed CSS. It is a CommonJS syntax utility, not a validator, browser compatibility layer, prefixer, module system, or plugin runner.
Do not install it for a new CSS tool; its own maintainers tell you to move to @adobe/css-tools. Keep it only where compatibility with the Rework AST is more valuable than modern syntax coverage, types, and planned bug fixes.
Use it if
- You maintain a Rework-era tool whose plugins already expect this package's exact AST node shapes
- You need a small synchronous CommonJS parser for CSS 2.1-era rules and the documented set of older at-rules
- You want simple parse, mutate, and stringify behavior without adopting a plugin architecture
- You need its non-enumerable parent links and source-position objects for an existing analysis script
- You are starting a new project: the README says there have been no material changes in ten years, no new changes or bug fixes are planned, and it explicitly recommends @adobe/css-tools
- You must parse current CSS broadly: the documented AST has fixed nodes for media, supports, keyframes and a few older at-rules, but no nodes for modern constructs such as @layer or @container
- You need TypeScript declarations: version 3.0.0 publishes none, so its many union-like AST shapes become your own typing project
- You need standards-aware validation or value parsing: declaration values remain trimmed strings and the package does not check whether a property, selector, or value is valid CSS
- You generate source maps in a sandbox or browser: input source maps are read by default and the implementation uses synchronous file-system access to resolve referenced maps
Setup reality
npm install css gives you a CommonJS module with parse and stringify; there are no peer dependencies, credentials, config files, binaries, or native compilation. The package does bring inherits, source-map 0.6, and source-map-resolve. Parsing and stringifying are synchronous, so large or hostile stylesheets block the event loop. Pass source when parsing if filenames and generated mappings matter. Without silent, the first syntax problem throws an Error carrying reason, filename, line, column, and the full source. With silent, parsing continues where its hand-written grammar can recover and errors collect under ast.stylesheet.parsingErrors; that is not the same as accepting valid modern CSS. Each AST node gets a non-enumerable parent property, and each position object inherits the full input through position.content. JSON.stringify omits those properties, which makes serialized trees look simpler than live ones. The parser only knows its documented node set, declaration values stay opaque strings, and comments embedded inside selectors, properties, or values are removed rather than round-tripped. Stringification defaults to readable output; compress strips comments and unnecessary whitespace. Sourcemap mode changes the return type from a string to an object with code and map. Input map resolution is enabled by default, can synchronously read files named by sourceMappingURL comments, and should be disabled with inputSourcemaps: false when file access is unwanted. No TypeScript declarations or ESM export map are included.
Patterns
Parse CSS into an ASTparse-stylesheet
const css = require('css');
const ast = css.parse('body { color: #222; }', {
source: 'styles/app.css',
});
console.log(ast.stylesheet.rules[0].type); // rulesource is optional, but supplying the real path produces useful filenames in errors and source maps.
Report a positioned syntax errorhandle-parse-error
try {
css.parse('a { color red }', { source: 'broken.css' });
} catch (error) {
console.error({
reason: error.reason,
file: error.filename,
line: error.line,
column: error.column,
});
}The default mode stops at the first parser error. error.source also contains the complete input string, which may be too much to log.
Collect errors without throwingcollect-parse-errors
const ast = css.parse(input, {
source: 'input.css',
silent: true,
});
for (const error of ast.stylesheet.parsingErrors) {
console.error(`${error.line}:${error.column} ${error.reason}`);
}silent only changes error handling. It does not make the grammar understand unsupported modern at-rules or guarantee a complete recovered tree.
Read selectors from top-level ruleslist-selectors
const selectors = ast.stylesheet.rules
.filter((node) => node.type === 'rule')
.flatMap((rule) => rule.selectors);
console.log(selectors);Nested rules under @media, @supports, @document, and @host are not included; recurse through their rules arrays when they matter.
Walk rules inside supported block at-ruleswalk-rules
function walk(nodes, visit) {
for (const node of nodes) {
visit(node);
if (Array.isArray(node.rules)) walk(node.rules, visit);
if (Array.isArray(node.keyframes)) walk(node.keyframes, visit);
if (Array.isArray(node.declarations)) walk(node.declarations, visit);
}
}
walk(ast.stylesheet.rules, (node) => {
if (node.type === 'declaration') console.log(node.property);
});There is no built-in walker. Different node types store children under rules, keyframes, or declarations, so a generic Object.keys recursion can accidentally follow parent links.
Rewrite declaration valueschange-declaration
walk(ast.stylesheet.rules, (node) => {
if (node.type === 'declaration' && node.property === 'color') {
node.value = 'rebeccapurple';
}
});
const output = css.stringify(ast);Values are opaque strings. The package will print your replacement without checking property grammar, units, functions, or browser support.
Remove comment nodes before outputremove-comments
function stripComments(nodes) {
return nodes
.filter((node) => node.type !== 'comment')
.map((node) => {
if (node.rules) node.rules = stripComments(node.rules);
if (node.declarations) node.declarations = stripComments(node.declarations);
if (node.keyframes) node.keyframes = stripComments(node.keyframes);
return node;
});
}
ast.stylesheet.rules = stripComments(ast.stylesheet.rules);Comments are explicit nodes only at rule and declaration boundaries. Comments embedded in selectors, properties, or values were already discarded during parsing.
Generate readable CSS with custom indentationstringify-readable
const output = css.stringify(ast, {
indent: ' ',
});
console.log(output);Stringification normalizes formatting rather than preserving the original whitespace and comment placement byte for byte.
Generate compact CSSstringify-compressed
const minified = css.stringify(ast, { compress: true });
console.log(minified);Compression removes comments and extra whitespace, but this is an old syntax printer rather than a modern optimizer that merges, rewrites, or validates rules.
Generate CSS and a serialized source mapgenerate-source-map
const ast = css.parse(input, { source: 'src/input.css' });
const result = css.stringify(ast, {
sourcemap: true,
inputSourcemaps: false,
});
console.log(result.code);
console.log(result.map);With sourcemap enabled, stringify returns { code, map } instead of a string. Parsing with source gives mappings a meaningful filename.
Return a SourceMapGeneratorget-map-generator
const result = css.stringify(ast, {
sourcemap: 'generator',
inputSourcemaps: false,
});
const json = result.map.toJSON();The generator is from the package's source-map 0.6 dependency, so methods and async behavior differ from newer source-map releases.
Serialize the enumerable AST safelyserialize-ast
const snapshot = JSON.stringify(ast, null, 2);
const restored = JSON.parse(snapshot);parent references and position.content are non-enumerable, so JSON.stringify avoids cycles but restored nodes lose parent links and the original source content used for maps.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @adobe/css-tools | npm | Use the repository's recommended successor for a familiar AST plus TypeScript support and current fixes |
| postcss | npm | Use the dominant plugin ecosystem when you need transforms, syntax extensions, warnings, or established tooling integrations |
| css-tree | npm | Use a detailed parser, walker, generator, and lexer when standards-aware analysis and validation matter |
| lightningcss | npm | Use a fast parser, transformer, prefixer, and minifier when native binaries are acceptable |