less review
Less compiles `.less` source into CSS. Its language adds variables, nesting, parametric mixins, guards, arithmetic, maps, loops, imports, and functions while accepting ordinary CSS syntax. The npm package includes the `lessc` CLI, a Promise-based `less.render` API for Node, and a browser build. Version 4.9.0 adds bracket lookup inside interpolated `@{...}` expressions, stops reparsing `escape()` results, and preserves media types while flattening nested media queries. Our package check found working CommonJS and ESM entry paths, but no TypeScript declarations and a sizable browser result.
Less 4.9.0 installed in 1.7 seconds and left 19 packages using 8 MB in our sandbox, while a browser import measured 158.5 KB minified and 51.5 KB gzipped. Keep it for stylesheets that depend on Less scoping and mixins; for new work, compare plain CSS, PostCSS, or Sass before adopting its global preprocessor model.
We installed it
| Install | ✓ · 1.7s | 19 packages on disk · 8 MB |
| Import | ✓ | ESM import works · require() works · ESM package with exports map |
| Browser | 51.5 KB | gzipped (158.5 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 less install cleanly?
Yes. In a fresh container with an empty cache, npm install less finished in 2 seconds, leaving 19 packages and 8 MB on disk. npm audit reported no known vulnerabilities.
How much does less add to a browser bundle?
51.5 KB gzipped (158.5 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does less work with both ESM and CommonJS?
Yes. Both import 'less' and require('less') worked in Node 22 in our run. The package is published as ESM with an exports map.
Does less include TypeScript types?
No type declarations were found in our install, so TypeScript users need their own declarations.
less or sass: which should you use?
sass: Use it for a new preprocessor codebase that needs namespaced modules through @use and @forward. Less 4.9.0 installed in 1.7 seconds and left 19 packages using 8 MB in our sandbox, while a browser import measured 158.5 KB minified and 51.5 KB gzipped.
When should you not use less?
A new project only needs custom properties and nesting. Current CSS plus a targeted PostCSS setup avoids Less scoping rules and a separate compiler language.
Use it if
- You maintain an existing Less theme and need its mixins, guards, imports, and variable override behavior to stay compatible.
- A Node build tool or document generator must compile user-selected variables through `less.render`.
- A framework integration already expects Less options such as `modifyVars`, `globalVars`, or `javascriptEnabled`.
- Your team wants a CSS-like preprocessor without adding a non-Node compiler to the toolchain.
- A new project only needs custom properties and nesting. Current CSS plus a targeted PostCSS setup avoids Less scoping rules and a separate compiler language.
- You need a module system that prevents globals from colliding. Less imports share variable and mixin scopes, and later definitions can change earlier references through lazy evaluation.
- Styles must compile in the end user's browser. Our full-package esbuild check produced 158.5 KB minified and 51.5 KB gzipped, before your stylesheet or application code.
- TypeScript code calls the compiler API and package-owned declarations are required. Version 4.9.0 ships no TypeScript types, so you need outside declarations or a local wrapper.
- The stylesheet still uses names or at-rule interpolation deprecated in 4.7 and 4.8. Less 5 plans to remove bare variables in certain at-rule positions, legacy identifier forms, and dynamic charset interpolation.
Setup reality
We installed Less 4.9.0 in a fresh Node 22 Bookworm container. npm succeeded in 1.7 seconds, left 19 packages using 8 MB, and reported no known vulnerabilities at any severity. The package declares 2 direct dependencies, no peer dependencies, 3,508 KB unpacked, Apache-2.0 licensing, and Node 18 or newer. It is ESM with an exports map; both require() and ESM import worked. No TypeScript declarations were present.
The normal production path compiles during a build or in Node. lessc comes with the package, while webpack needs less-loader and Vite only needs Less installed before it can process .less imports. Pass filename to less.render when the input is a string, because relative imports and URL rewriting need a base path. Static minification requires a plugin such as less-plugin-clean-css rather than a core lessc switch.
Less 4 defaults to parens-division, so @width / 2 outside parentheses may remain CSS instead of becoming a calculated value. Inline backtick JavaScript is disabled unless javascriptEnabled is set. Older themes often depend on both behaviors. Variable resolution is lazy and the last definition in a scope wins, which makes modifyVars useful for theming but can make import order surprising.
The package can run in a browser, though our esbuild import measured 158.5 KB minified and 51.5 KB gzipped. Compile ahead of time unless runtime theme generation truly needs the compiler. Version 4.9.0 changes interpolation lookup and fixes escape() plus nested media output, so compare generated CSS when upgrading a theme that uses those constructs.
Patterns
Compile an entry file and source map compile-with-cli
npx lessc src/styles.less dist/styles.css
npx lessc --source-map=dist/styles.css.map src/styles.less dist/styles.css
npm install --save-dev less-plugin-clean-css
npx lessc --clean-css src/styles.less dist/styles.min.cssCore Less does not include a minifier. The clean-css flag works only after its plugin is installed, and source maps require an explicit option.
Compile a Less string from Node render-in-node
import less from 'less';
import { readFile } from 'node:fs/promises';
const input = await readFile('src/styles.less', 'utf8');
const output = await less.render(input, {
filename: 'src/styles.less',
paths: ['src', 'node_modules'],
math: 'parens-division',
});
console.log(output.css);
console.log(output.imports);Set `filename` even for string input. It anchors relative imports, URL rewriting, source-map locations, and the returned dependency list.
Make division explicit under Less 4 control-division
@gutter: 24px;
.card {
padding: (@gutter / 2);
width: @gutter * 4;
}The default `parens-division` mode divides inside parentheses. A bare slash may be preserved for native CSS instead of raising an error.
Account for lazy, last-wins variables scope-variables
@brand: blue;
.header {
color: @brand;
@brand: green;
}
@brand: red;
.footer { color: @brand; }The header resolves to green because the later definition wins inside that scope. Imports can create the same silent collision across files.
Use parameters and a guard in a mixin define-guarded-mixin
.button-variant(@bg; @radius: 4px) when (iscolor(@bg)) {
background: @bg;
border-radius: @radius;
color: if((lightness(@bg) > 50%), #111, #fff);
}
.btn-primary { .button-variant(#2563eb); }
.btn-danger { .button-variant(#dc2626; 8px); }Semicolons separate mixin arguments safely. Commas can belong to a CSS value and may be parsed as one argument.
Choose how each imported file behaves control-import-output
@import (reference) '../theme/variables.less';
@import (optional) './local-overrides.less';
@import (css) 'https://fonts.example.com/x.css';
@import (inline) './legacy.css';A reference import exposes variables and mixins without emitting all of its rules. Inline mode copies content without asking the Less parser to understand it.
Interpolate names and map lookups interpolate-lookup
@sizes: { sm: 4px; md: 8px; };
@choice: md;
@prefix: card;
.@{prefix} {
gap: @{sizes}[@{@choice}];
}Version 4.9.0 adds bracket lookups inside `@{...}` interpolation. Check the exact syntax when a dynamic key comes from another variable.
Share a rule through selector extension extend-selector
.button { padding: 8px 16px; border: 0; }
.button:hover { opacity: .9; }
.button-link {
&:extend(.button all);
background: none;
}Extension adds selectors to existing output and keeps the original rule position. That source order can behave differently from copying declarations with a mixin.
Create utility rules from a detached map generate-from-map
@spaces: {
sm: 4px;
md: 8px;
lg: 16px;
};
each(@spaces, {
.p-@{key} { padding: @value; }
});
.stack { gap: @spaces[md]; }Inside `each`, Less supplies `@key`, `@value`, and `@index`. Generated utility sets increase both compile work and final CSS.
Compile a theme with variable overrides override-theme-vars
import less from 'less';
async function buildTheme(primary) {
const { css } = await less.render('@import "theme/index.less";', {
filename: 'virtual.less',
paths: ['src'],
modifyVars: { '@primary-color': primary },
});
return css;
}`modifyVars` places overrides after the source variables. Cache repeated results; recompiling an entire theme for each request wastes CPU.
Pass Less options through Vite configure-vite
// vite.config.js
export default {
css: {
preprocessorOptions: {
less: {
math: 'parens-division',
javascriptEnabled: false,
},
},
},
};Install Less in the project and Vite detects `.less` imports. Enable inline JavaScript only for a reviewed legacy theme that needs it.
Add a compiler function with a plugin register-plugin-function
// double-plugin.cjs
module.exports = {
install(less, pluginManager, functions) {
functions.add('double', (value) =>
new less.tree.Dimension(value.value * 2, value.unit));
},
};
// styles.less
@plugin './double-plugin.cjs';
.box { width: double(20px); }Plugin functions receive and return Less tree nodes. A reviewed plugin is safer than turning on arbitrary backtick JavaScript in stylesheets.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| sass | npm | Use it for a new preprocessor codebase that needs namespaced modules through `@use` and `@forward`. |
| postcss | npm | Use it to keep CSS syntax and apply only selected transforms such as nesting or autoprefixing. |
| stylus | npm | Use it when optional-brace syntax and a JavaScript plugin API outweigh its smaller current community. |
| lightningcss | npm | Use it for fast CSS transforms, minification, and browser-target handling without Less language features. |
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.

