
Research
Malicious npm Packages Impersonate Flashbots SDKs, Targeting Ethereum Wallet Credentials
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
Need to write CSS, but tired of SASS, LESS et al? Try JavaScript/TypeScript in CSS.
This library serves much of the same purpose as SASS/SCSS, LESS and other CSS preprocessors, but uses plain JavaScript/TypeScript to provide type-safety and "programmability" with local variables, mixins, utility functions, etc. etc.
Overall this is a "do less" toolkit with tiny API, that mainly tries to stay out of your way.
SCSS-like selector nesting, inline
// comments support
and autoprefixer features are
automatically provided by postCSS, but apart from that it's all pretty
basic. Just you composing CSS.
For good developer experience, use VSCode and install the official
vscode-styled-components extension. That gives
you instant syntax highlighting and IntelliSense autocompletion inside
css`` template literals, and maybe add a few
helpful "snippets".
See also the chapter Why es-in-css Instead of SASS? below.
Table of Contents:
yarn add --dev es-in-css
Create a file called src/cool-design.css.js:
import { css, makeVariables, px } from 'es-in-css';
const colors = {
yellow: `yellow`,
red: `#cc3300`,
purple: `#990099`,
};
const bp = { large: 850 };
const mq = {
small: `screen and (max-width: ${px(bp.large - 1)})`,
large: `screen and (min-width: ${px(bp.large)})`,
};
const cssVars = makeVariables([
'linkColor',
'linkColor--hover',
'linkColor__focus',
'focusColor',
]);
const vars = cssVars.vars;
export default css`
:root {
${cssVars.declare({
linkColor: colors.red,
'linkColor--hover': colors.purple, // dashes must be quoted
linkColor__focus: var.focusColor, // aliased
focusColor: `peach`,
})}
}
a[href] {
color: ${vars.linkColor};
unknown-property: is ok;
&:hover {
color: ${vars['linkColor--hover']};
}
&:focus-visible {
color: ${vars.linkColor__focus};
}
}
@media ${mq.large} {
html {
background-color: ${colors.yellow};
}
}
`;
Then build/compile the CSS file with the command:
yarn run es-in-css "src/*.css.js" --outdir=dist/styles
or using npm:
npm exec es-in-css "src/*.css.js" --outdir=dist/styles
You now have a file called dist/styles/cool-design.css:
:root {
--linkColor: #cc3300;
--linkColor--hover: #990099;
--linkColor__focus: var(--focusColor);
--focusColor: peach;
}
a[href] {
color: var(--linkColor);
unknown-property: is ok;
}
a[href]:hover {
color: var(--linkColor--hover);
}
a[href]:focus-visible {
color: var(--linkColor__focus);
}
@media screen and (min-width: 850px) {
html {
background-color: yellow;
}
}
The es-in-css module exports the following methods:
css TemplaterSyntax: css`...`: string
Dumb(-ish) tagged template literal that returns a string. It provides nice
syntax highlighting and code-completion in VSCode (with an
extension) by using a well-known name.
Example of use:
import { css } from 'es-in-css';
const themeColors = {
yellow: `yellow`,
red: `#cc3300`,
purple: `#990099`,
};
const textColor = `#333`;
const boxStyle = () => css`
background: #f4f4f4;
border: 1px solid #999;
border-radius: 3px;
padding: 12px;
`;
export default css`
body {
color: ${textColor};
${boxStyle};
}
${Object.entries(themeColors).map(
(color) => css`
body.theme--${color.name} {
background-color: ${color.value};
}
`
)}
`;
Depsite being quite "dumb" it does have some convenience features:
0) similar to how React
behaves.All other values are cast to string as is.
cssVal TemplaterSyntax: Same as css Templater
An alias for the css`` templater, for cases where you're writing a
standalone CSS value or other out-of-context CSS snippets, and you wish to
disable VSCode's syntax highlighting and error checking.
str Quoted String PrinterSyntax: str(value: string): string
Helper to convert a value to a quoted string.
Example:
import { str, css } from 'es-in-css';
const message = 'Warning "Bob"!';
export default css`
.foo::before {
content: ${str(message)};
}
`;
// Outputs:
//
// .foo::before {
// content: "Warning \"Bob\"!";
// }
scoped Name GeneratorSyntax: scoped(prefix?: string): string
Returns a randomized/unique string token, with an optional prefix. These
tokens can be using for naming @keyframes or for mangled class-names, if
that's what you need:
import { scoped, css } from 'es-in-css';
export const blockName = scoped(`Button`); // 'Button_4af51c0d267'
export default css`
.${blockName} {
border: 1px solid blue;
}
.${blockName}__title {
font-size: 2rem;
}
`;
// Outputs:
//
// .Button_4af51c0d267 {
// border: 1px solid blue;
// }
// .Button_4af51c0d267__title {
// font-size: 2rem;
// }
Fixed/Physical sizes: px() and cm()
Font relative: em(), rem(), ch() and ex()
Layout relative: pct() (%), vh(), vw(), vmin() and vmax()
Time: ms()
Angle: deg()
These return light-weight UnitValue instances that can behave as either
string or number literals, depending on the context.
(See the unitVal helper for more info)
import { px, css } from 'es-in-css';
const leftColW = px(300);
const mainColW = px(700);
const gutter = px(50);
// Calculations work as if they're numbers
const totalWidth = px(leftColW + gutter + mainColW);
export default css`
.layout {
/* Unit suffix appears when printed. */
width: ${totalWidth};
margin: 0 auto;
display: flex;
gap: ${gutter};
}
.main {
width: ${mainColW};
}
.sidebar {
width: ${leftColW};
}
`;
// .layout {
// /* Unit suffix appears when printed. */
// width: 1050px;
// margin: 0 auto;
// display: flex;
// gap: 50px;
// }
// .main {
// width: 700px;
// }
// .sidebar {
// width: 300px;
// }
unitVal HelperSyntax:
unitVal<U extends string>(value: number | UnitValue<U>, unit: U): UnitValue<U> & number
Creates a custom UnitValue instance that is also number-compatible (see
Unit Value Types for more info)
import { unitVal, px } from 'es-in-css';
// These are the same
const valA = /** @type {number & UnitValue<'px'>} */ unitVal(10, 'px');
const valB = /** @type {number & UnitValue<'px'>} */ px(10);
// Both have `.value` and `.unit`
valA.value === 10; // true
valA.unit === 'px'; // true
valB.value === 10; // true
valB.unit === 'px'; // true
// Both string-print with the unit attached
`${valA}` === '10px'; // true
`${valB}` === '10px'; // true
// Both behave as numbers
valA * 2 === 20; // true;
valB * 2 === 20; // true;
// And are assignable to numbers
const numA = /** @type {number} */ valA;
const numB = /** @type {number} */ valB;
// And behave like numbers
const minVal = Math.min(valA, valB);
// ...except under close scrutiny
typeof valB === 'number'; // ❌ false
The unit value helpers emit the following UnitValue
sub-types:
PxValue, RemValue, EmValue, ChValue, ExValue, PctValue, VwValue,
VhValue, VminValue, VmaxValue, MsValue, CmValue, DegValue,
FrValue
All of these unit types are also typed as a number, to tell TypeScript that
the values are safe to use in calculations. (They are safe because they have a
number-returning .valueOf() method.)
NOTE: This white "lie" about the number type may cause problems at
runtime if these "UnitNumbers" end up in situations where
typeof x === "number" is used to validate a literal number value.
However, the risk vs. benefit trade-off seems reasonable.
For cases that require an actual non-unit, plain number value, you can use
the PlainNumber type. Example:
import { PlainNumber, PxValue, rem } from 'es-in-css';
/** Converts a pixel size to a rem value. */
export const pxRem = (px: PlainNumber | PxValue) => rem(px / 16);
Additionally, there are helpful categorized union types:
LayoutRelativeValue – all container proportional units: %, vw, etc.FontRelativeValue – all text proportional units: em, rem, etc.LengthValue – all fixed/physical units (px, cm), plus the above
unions.To keep it simple and sane es-in-css only supports one UnitValue type
per category of units (time, angles, physical size, etc.) but provides
friendly converter functions from other units of measure into the main
supported units.
Percentage values from proportions/fractions:
pct_f(), vh_f(), vw_f(), vmin_f() and vmax_f().
pct_f(1 / 3); // 33.33333% (Same as `pct(100 * 1/3)`)
vw_f(370 / 1400); // 26.42857143vw
Milliseconds from seconds:
ms_sec()
ms_sec(1.2); // 1200ms
Centimeters from other physical units:
cm_in(), cm_mm(), cm_pt() and cm_pc().
cm_mm(33.3); // 3.33cm
cm_in(1); // 2.54cm
Degrees from other angle units:
deg_turn(), deg_rad(), deg_grad(),
deg_turn(0.75); // 270deg
deg_rad(-Math.PI); // -180deg
unitOf HelperSyntax:
unitOf<U extends string>(value: number | UnitValue<U>): U | undefined
Checks if its given argument is a UnitValue instance and returns its .unit
property.
import { unitOf } from 'es-in-css';
unitOf(px(10)); // 'px'
unitOf(ms_sec(1)); // 'ms'
Returns undefined otherwise.
unitOf(10); // undefined
unitOf('10px'); // undefined
es-in-css bundles the color package
and re-exports it as color.
The color class/function creates ColorValue instances that can be used in
CSS, but also come with useful manipulation mhethods.
import { color, css } from 'es-in-css';
const c1 = color('red');
const c2 = c1.fade(0.8).desaturate(0.5);
export default css`
div {
color: ${c1};
background-color: ${c2};
}
`;
// div {
// color: rgb(255, 0, 0);
// background-color: hsla(0, 50%, 50%, 0.2);
// }
It extends color by adding a static fromName method to generate a
type-safe color-name to ColorValue mapper:
const prettyColor = color.fromName('lime');
// is just a alias for
const prettyColor2 = color('lime');
// but adds type-safety that the base method lacks:
const notAColor2 = color('bogus'); // ❌ Type-checks but throws at runtime
const notAColor = color.fromName('bogus'); // Type Error
// ^^^^^^^
It also exports rgb() and hsl() which are simple aliases of the color
package's static class methods of the same names.
import { rgb, hsl, color } from 'es-in-css';
const rgbRed = rgb(255, 0, 0);
const hslRed = hsl(0, 100, 50);
// With alpha channel
const rgbRedFaded = rgb(255, 0, 0, 0.5);
const hslRedFaded = hsl(0, 100, 50, 0.5);
rgb === color.rgb; // true
hsl === color.hsl; // true
Feel free to import your own color helper library, and use it instead.
makeVariables HelperSyntax:
makeVariables<T extends string>(variableTokens: Array<T>, options?: VariableOptions): VariableStyles<T>
Helper to provide type-safety and code-completion when using CSS custom properties (CSS variables) at scale.
See VariableOptions below for configuration options.
import { makeVariables, css } from 'es-in-css';
const myVarNames = ['linkColor', 'linkColor__hover'];
const cssVars = makeVariables(myVarNames);
The returned VariableStyles object contains the following properties:
VariableStyles.vars.* — pre-declared CSS value printers (outputting
var(--*) strings)VariableStyles.declare(…) — for declaring initial values for all of the
variables.VariableStyles.override(…) — for re-declaraing parts of the variable
collection.VariableStyles.varsSyntax: VariableStyles<T>.vars: Record<T, VariablePrinter>
Holds a readonly Record<T, VariablePrinter> object where the
VariablePrinters emit the CSS variable names wrapped in var(), ready to be
used as CSS values … with the option of passing a default/fallback value via
the .or() method.
const { vars } = cssVars;
vars.linkColor + ''; // invokes .toString()
// `var(--linkColor)`
vars.linkColor.or(`black`); // pass "black" as fallback value
// `var(--linkColor, black)`
`color: ${vars.linkColor__hover};`;
// `color: var(--linkColor__hover);`
VariablePrinter objects also have a cssName property with the raw
(unwrapped) name of the variable, like so:
vars.linkColor.cssName;
// `"--linkColor"`
VariableStyles.declareSyntax: VariableStyles<T>.declare(vars: Record<T, string >): string
Lets you type-safely write values for all the defined CSS variables into a
CSS rule block. Property names not matching T are dropped/ignored.
css`
:root {
${cssVars.declare({
linkColor: `#0000cc`,
linkColor__hover: `#cc00cc`,
unknown_variable: `transparent`, // ignored/dropped
})}
}
`;
// :root {
// --linkColor: #0000cc,
// --linkColor__hover: #cc00cc,
// }
VariableStyles.overrideSyntax:
VariableStyles<T>.override(vars: Partial<Record<T, string >>): string
Similar to the .declare() method, but can be used to re-declare (i.e.
override) only some of of the CSS variables T. Again, property names not
matching T are ignored/dropped.
Furthermore, values of null, undefined, false are interpreted as
"missing", and the property is ignored/dropped.
css`
@media (prefers-color-scheme: dark) {
:root {
${cssVars.override({
linkColor: `#9999ff`,
unknown_variable: `#transparent`, // ignored/dropped
linkColor__hover: false, // ignored/dropped
})}
}
}
`;
// @media (prefers-color-scheme: dark) {
// :root{
// --linkColor: #9999ff;
// }
// }
makeVariables.join Composition HelperSyntax:
makeVariables.join(...varDatas: Array<VariableStyles>): VariableStyles
This helper combines the variable values and declaration methods from multiple
VariableStyles objects into a new, larger VariableStyles object.
const colorVariables = makeVariables(['primary', 'secondary', 'link'], {
namespace: 'color-',
});
const fontVariables = makeVariables(['heading', 'normal', 'smallprint'], {
namespace: 'font-',
});
const allVariables = makeVariables.join(colorVariables, fontVariables);
css`
p {
color: ${allVariables.vars.primary};
font: ${allVariables.vars.normal};
}
`;
// p {
// color: var(--color-primary);
// font: var(--font-normal);
// }
makeVariables.isVar HelperSyntax: makeVariables.isVar(value: unknown): value is VariablePrinter
A helper that checks if an input value is of type VariablePrinter.
import { makeVariables } from 'es-in-css';
makeVariables.isVar(cssVars.vars.linkColor); // ✅ true
// None of these are `VariablePrinter` objects:
makeVariables.isVar('var(--linkColor)'); // ❌ false
makeVariables.isVar('' + cssVars.vars.linkColor); // ❌ false
makeVariables.isVar(cssVars); // ❌ false
VariableOptionsBy default only "simple" ascii alphanumerical variable-names are allowed
(/^[a-z0-9_-]+$/i). If unsuppored/malformed CSS variable names are passed,
the function throws an error. However, you can author your own RegExp to
validate the variable names, and a custom CSS variable-name mapper:
VariableOptions.nameRe?: RegExp
Custom name validation RegExp, for if you want/need to allow names more complex than the default setting allows.
(Default: /^[a-z0-9_-]+$/i)
// Default behaviour rejects the 'ö' character
const var1 = makeVariables(['töff']); // ❌ Error
// Set custom pattern allowing a few accented letters.
const var2opts: VariableOptions = { nameRe: /^[a-z0-9_-áðéíóúýþæö]+$/i };
const var2 = makeVariables(['töff'], var2opts); // ✅ OK
var2.vars.töff + ''; // `var(--töff)`
VariableOptions.toCSSName?: (name: string) => string
Maps weird/wonky JavaScript property names to CSS-friendly css custom property names.
(Default: (name) => name)
const var3opts: VariableOptions = {
// convert all "_" to "-"
toCSSName: (name) => name.replace(/_/g, '-'),
};
const var3 = makeVariables(['link__color'], var3opts);
var3.declare({ link__color: 'blue' }); // `--link--color: blue;\n`
var3.vars.link__color + ''; // `var(--link--color)`
VariableOptions.namespace?: string
Prefix that gets added to all CSS printed variable names.
The namespace is neither validated nor transformed in any way, except that spaces and other invalid characters are silently stripped away.
const var4opts: VariableOptions = {
// NOTE: The "{" and "}" will get silently stripped
namespace: ' ZU{U}PER-',
};
const var4 = makeVariables(['link__color'], var4opts);
var4.declare({ link__color: 'blue' }); // `--ZUUPER-link--color: blue;\n`
var4.vars.link__color + ''; // `var(--ZUUPER-link--color)`
The es-in-css compiler imports/requires the default string export of the
passed javascript modules and passes it through a series of postcss plugins
before writing the resulting CSS to disc.
The es-in-css package exposes a CLI script of the same name. (Use yarn run
or npm exec to run it, unless you have ./node_modules/.bin/ in PATH, or
es-in-css is installed "globally".)
es-in-css "inputglob" --outbase=src/path --outdir=out/path --minify
inputglob
Must be quoted. Handles all the patterns supported by the
glob module.
-d, --outdir <path>
By default the compiled CSS files are saved in the same folder as the source
file. This is rarely the desired behavior so by setting outdir you choose
where the compiled CSS files end up.
The output file names replace the input-modules file-extension with .css —
unless if the source file name ends in .css.js, in which case the .js
ending is simply dropped.
-b, --outbase <path>
If your inputglob file list contains multiple entry points in separate
directories, the directory structure will be replicated into the outdir
starting from the lowest common ancestor directory among all input entry point
paths.
If you want to customize this behavior, you should set the outbase path.
-e, --ext <file-extension>
Customize the file-extension of the output files. Default is .css
-m, --minify
Opts into moderately aggressive, yet safe cssnano minification of the resulting CSS.
All comments are stripped, except ones that start with /*!.
-p, --prettify [configFilePath]
Runs the result CSS through Prettier. Accepts optional configFilePath, but
defaults to resolving .prettierrc for --outdir or the current directory.
Ignored if mixed with --minify.
-n, --no-nested
Disables the SCSS-like selector nesting behavior provided by the postcss-nested plugin.
(To pass custom options to the plugin, use the JavaScript API.)
es-in-css "src/css/**/*.js" --outdir=dist/styles
Given the src folder contained the following files:
src/css/styles.css.js
src/css/resets.js
src/css/component/buttons.css.js
src/css/component/formFields.js
The dist folder now contains:
dist/styles/styles.css
dist/styles/resets.css
dist/styles/component/buttons.css
dist/styles/component/formFields.css
Note how the src/css/ is automatically detected as a reasonable common
ancestor. If you want to make src/ the base folder, you must use the
outbase option, like so:
es-in-css "src/css/**/*.js" --outbase=src --outdir=dist/styles
The dist folder now contains:
dist/styles/css/styles.css
dist/styles/css/resets.css
dist/styles/css/component/buttons.css
dist/styles/css/component/formFields.css
The options for the JavaScript API are the same as for the CLI, with the following additions:
write?: boolean — (Default: true) Allows turning off the automatic
writing to disc, if you want to post-process the files and handle the FS
writes manually.redirect?: (outFile: string, inFile: string) => string | undefined —
Dynamically changes the final destination of the output files. (Values that
lead to overwriting the source file are ignored.)banner?: string — Text that's prepended to every output file.footer?: string — Text that's appended to every output file.ext?: string | (inFile: string) => string | undefined — The function
signature allows dynamically choosing a file-extension for the output files.nesting?: boolean | import('postcss-nesting').Options — (Default: true)
Allows turning off the SCSS-like selector nesting behavior provided by
postcss-nested or passing it
custom options.compileCSS (from files)Works in pretty much the same way as the CLI.
Takes a list of files to read, and returns an Array of result objects each containing the compiled CSS and the resolved output file path.
const { compileCSS } = require('es-in-js/compiler');
const { writeFile } = require('fs/promise');
const files = [
'src/foo/styles.css.js',
'src/foo/styles2.css.js',
]
compileCSS(sourceFiles, {
outbase: 'src'
outdir: 'dist'
// ext: '.scss', // default: '.css'
// ext: (inFile) => inFile.endsWith('.scss.js') ? 'scss' : 'css',
// minify: false,
// prettify: false,
// redirect: (outFile, inFile) => outFile + '_',
write: false,
}).then((result) => {
console.log(result.inFile); // string
writeFile(result.outFile, result.css);
});
compileCSSFromJSCompiles CSS from a JavaScript source string. This may be the preferable
method when working with bundlers such as esbuild.
(NOTE: This method temporarily writes the script contents to the file system to allow imports and file-reads to work correctly, but then deletes those files afterwards.)
const { compileCSSFromJS } = require('es-in-js/compiler');
const { writeFile } = require('fs/promise');
const scriptStrings = [
{
fileName: '_temp/styles.css.mjs',
content: `
import { css } from 'es-in-css';
export const baseColor = 'red';
export default css\`
body { color: \${baseColor}; }
\`;
`,
},
{
fileName: '_temp/styles2.css.mjs',
content: `
import { css } from 'es-in-css';
import { baseColor } from './styles.css.mjs';
export default css\`
div { color: \${baseColor}; }
\`;
`,
},
];
compileCSSFromJS(scriptStrings, {
outbase: 'src',
outdir: 'dist',
// ext: '.css',
// minify: false,
// prettify: false,
// redirect: (outFile, inFile) => outFile + '_',
write: false,
}).then((result) => {
console.log(result.inFile); // string
writeFile(result.outFile, result.css);
});
compileCSSStringLower-level method that accepts a raw, optionally nested, CSS string (or an array of such strings) and returns a compiled CSS string (or array) — optionally minified or prettified.
const { compileCSSString } = require('es-in-js/compiler');
const rawCSS = `
// My double-slash comment
body {
p { color: red;
> span { border:none }
}
}
`;
compileCSSString(rawCSS, {
prettify: true,
// outdir: 'dist', // Used for auto-resolving .prettierrc
// minify: false,
// nesting: true,
// banner: '',
footer: '/* The "footer" is appended as is */',
}).then((outCSS) => {
console.log(outCSS);
// /* My double-slash comment */
// body p {
// color: red;
// }
// body p > span {
// border: none;
// }
// /* The "footer" is appended as is */
});
TL;DR: JavaScript/TypeScript provides better developer ergonomics than SASS, and is a more future-proof technology.
SASS has been almost an industry standard tool for templating CSS code for well over a decade now. Yet it provides poor developer experience with lackluster editor integrations, idiosyncratic syntax, extremely limited feature set, publishing and consuming libraries is hard, etc…
Over the past few years, the web development community has been gradually moving on to other, more nimble technologies — either more vanilla "text/css" authoring, or class-name-based reverse compilers like Tailwind, or various CSS-in-JS solutions.
This package provides supportive tooling for this last group, but offers also a new lightweight alternative: To author CSS using JavaScript as a templating engine, and then output it via one of the following methods:
writeFile the resulting string to static fileHere are a few code "snippets" you can add to your global snippets file to help you use es-in-css a bit faster:
"Insert ${} variable print block": {
"scope": "javascript,javascriptreact,typescript,typescriptreact,css",
"prefix": "v",
"body": "\\${$0}",
},
"css`` tagged template literal": {
"scope": "javascript,javascriptreact,typescript,typescriptreact",
"prefix": "css",
"body": "css`\n\t$0\n`",
},
"cssVal`` tagged template literal": {
"scope": "javascript,typescript,typescriptreact",
"prefix": "cssVal",
"body": "cssVal`\n\t$0\n`",
},
"New *.css.js file": {
"scope": "javascript,typescript",
"prefix": "css-js-file",
"body": [
"import { css } from 'es-in-css';",
"",
"export default css`\n\t$0\n`"],
},
Also make sure you install the official vscode-styled-components
extension for fancy syntax highlighting and
IntelliSense autocompletion inside css`` template literals
Maybes:
--watch mode (may be out of scope?)Not planned:
This project uses the Bun runtime for development (tests, build, etc.)
PRs are welcoms!
See CHANGELOG.md
0.7.8
2025-05-08
#__NO_SIDE_EFFECTS__ compiler notation to all exported functionsFAQs
Need to write CSS, but tired of SASS, LESS et al? Try JavaScript/TypeScript in CSS.
The npm package es-in-css receives a total of 138 weekly downloads. As such, es-in-css popularity was classified as not popular.
We found that es-in-css demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer 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.

Research
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.

Security News
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.

Security News
Following last week’s supply chain attack, Nx published findings on the GitHub Actions exploit and moved npm publishing to Trusted Publishers.