mrkeyoor.com_
Wed 23 Sept 00:34 UTC
npmWeb Frontendupdated 22 Sept 2026

nano-css review

Our install found a CSS-in-JS construction kit, not a configured styling library. nano-css 5.6.2 starts with create() and put(), which turn style objects into injected browser rules or server-collected CSS. Generated classes, sheets, stable hashes, nesting, keyframes, React components, prefixing, RTL conversion, hydration, and extraction arrive through addons or presets. That lets a team assemble a narrow renderer, but it also makes addon order, server isolation, and client hydration part of the application's own design.

Verdict

nano-css 5.6.2 installed 22 packages and 18 MB in 3.2 seconds on our box, while its whole-package probe bundled to 1.5 KB minified. Keep it where a team already owns the addon graph; new projects should choose it only after testing SSR hydration, CSP, and peer behavior.

We installed it

Lab card: what happened when we installed nano-cssScreenshot of nano-css documentation
Install✓ · 3.2s22 packages on disk · 18 MB
ImportESM import works · require() works · CommonJS package
Browser0.9 KBgzipped (1.5 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does nano-css install cleanly?

Yes. In a fresh container with an empty cache, npm install nano-css finished in 3 seconds, leaving 22 packages and 18 MB on disk. npm audit reported no known vulnerabilities.

How much does nano-css add to a browser bundle?

0.9 KB gzipped (1.5 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does nano-css work with both ESM and CommonJS?

Yes. Both import 'nano-css' and require('nano-css') worked in Node 22 in our run. The package is published as CommonJS.

Does nano-css include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

nano-css or goober: which should you use?

goober: Use it for a small generated-class API with less addon assembly. nano-css 5.6.2 installed 22 packages and 18 MB in 3.2 seconds on our box, while its whole-package probe bundled to 1.5 KB minified.

When should you not use nano-css?

You expect installation to provide the README's entire feature list; core preinstalls put() while most APIs are separate addons

API stability4/5The renderer-and-addon model has remained on major version 5 since 2019, and create(), put(), rule(), and sheet() retain their established roles. Addons can wrap or replace methods and their installation order affects results, so the surface is wider than the small core implies. Type coverage also differs across entry points. A local preset module that installs one known sequence is the safest boundary for an application.
Docs3/5The repository documents installation, presets, SSR, hydration, extraction, and dozens of individual addons. It records useful limits such as lazy sheet insertion and hydration gaps for keyframes and media queries. The material shows its age: the headline 0.5 KB link measures version 1.15.3, examples predate current React patterns, and wildcard React peer consequences in the published 5.6.2 metadata are not explained.
Maintenance3/5The repository was pushed on February 16, 2026 and is not archived, but npm 5.6.2 dates to July 2024. GitHub shows 33 open issues and pull requests. Dependency work continues, while feature releases and the documented development stack move slowly for a runtime styling library. That is acceptable for an existing pinned system, with less confidence for a new stack expected to follow React and SSR changes.
Ecosystem3/5npm counted 3,542,896 downloads during August 18 through August 24, 2026. The package contains addons for React, SSR, atomic styles, prefixing, RTL, animations, source maps, and CSSOM use. Its 446 GitHub stars and mostly first-party integration catalog point to a smaller direct community than Emotion or styled-components, and transitive downloads should not be confused with new adoption.

Use it if

  • You need runtime CSS rule insertion without wrapper components or inline style attributes
  • A framework-neutral renderer matters and you are prepared to install only the addons you use
  • Server-collected CSS and stable client class names must come from the same low-level engine
  • An existing nano-css v5 application already relies on its presets, rule, sheet, jsx, or RTL behavior
Skip it if

Setup reality

Our Node 22 sandbox installed nano-css 5.6.2 in 3.2 seconds. It left 22 packages and 18 MB on disk, with 8 direct dependencies and 2 peer dependencies. npm audit found 0 known vulnerabilities. The package is 560 KB unpacked, carries the Unlicense, bundles TypeScript declarations, and is CommonJS without an exports map. Both require() and ESM import still worked in our test.

The two peers are react and react-dom with wildcard ranges, even though create() itself is described as framework neutral. Modern npm may install or complain about those peers for a standalone consumer. Only put() is present on a fresh renderer. The sheet preset adds stable names, nesting, atoms, keyframes, rule, and sheet; the React preset installs a broader chain and calls React.createElement.

In a browser, nano-css appends a style element to document.head unless sh points to one you created. Production uses insertRule(), while development writes readable text and creates an extra test sheet. Set NODE_ENV consistently. Our whole-package browser import measured 1.5 KB minified and 0.9 KB gzipped, but each chosen preset and addon changes what the application actually ships.

Create a new renderer for every SSR request, execute lazy sheet rules before reading nano.raw, and use stable naming on both sides. Reusing one renderer across requests keeps accumulated CSS. Client hydration needs the matching style element and does not restore media queries or keyframes. Because addons wrap renderer methods, their documented dependencies and installation order are observable behavior rather than optional organization.

Patterns

Apply a renderer and inject a selector inject-global-rule

import { create } from 'nano-css';

const nano = create({ pfx: 'acme-' });

nano.put('.notice', {
  color: 'tomato',
  border: '1px solid currentColor',
});

The inject global rule path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Generate a class from a style object generate-class-name

import { create } from 'nano-css';
import { addon as addonStable } from 'nano-css/addon/stable';
import { addon as addonRule } from 'nano-css/addon/rule';

const nano = create();
addonStable(nano);
addonRule(nano);

const className = nano.rule({ color: 'tomato' });

The generate class name path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Define several lazily inserted classes create-style-sheet

import { preset } from 'nano-css/preset/sheet';

const { sheet } = preset({ pfx: 'acme-' });
const styles = sheet({
  input: { border: '1px solid #aaa' },
  button: { color: 'white', background: 'navy' },
}, 'ContactForm');

button.className = styles.button;

The create style sheet path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Apply parent references and grouped selectors nest-selectors

import { preset } from 'nano-css/preset/sheet';

const nano = preset();
nano.put('.menu', {
  '&:hover': { color: 'blue' },
  '.icon, .label': { opacity: 0.8 },
  '.dark &': { color: 'white' },
});

The nest selectors path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Generate a responsive class add-media-query

import { preset } from 'nano-css/preset/sheet';

const { rule } = preset();
const card = rule({
  display: 'grid',
  gridTemplateColumns: '1fr 1fr',
  '@media (max-width: 640px)': {
    gridTemplateColumns: '1fr',
  },
}, 'Card');

The add media query path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Apply a uniquely named animation create-keyframes

import { preset } from 'nano-css/preset/sheet';

const { keyframes, rule } = preset();
const spin = keyframes({
  '0%': { transform: 'rotate(0deg)' },
  '100%': { transform: 'rotate(360deg)' },
});
const spinner = rule({ animation: `${spin} 800ms linear infinite` });

The create keyframes path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Build a prop-driven React button create-react-component

import { preset } from 'nano-css/preset/react';

const { styled } = preset({ pfx: 'acme-' });

const Button = styled.button(
  { border: 0, padding: '8px 12px' },
  (props) => ({
    color: 'white',
    background: props.danger ? 'crimson' : 'royalblue',
  }),
  'Button',
);

The create react component path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Apply one-off styles through a styling block override-component-styles

import { preset } from 'nano-css/preset/react';

const { jsx } = preset();
const Panel = jsx('section', { padding: '16px', background: '#fff' }, 'Panel');

export function Warning() {
  return <Panel css={{ borderLeft: '4px solid orange' }}>Check input</Panel>;
}

The override component styles path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Collect CSS during server rendering render-server-css

import { preset } from 'nano-css/preset/sheet';

export function renderStyles() {
  const nano = preset({ pfx: 'acme-' });
  const title = nano.rule({ fontWeight: 700 }, 'Title');
  const markup = `<h1 class="${title}">Hello</h1>`;
  return `${markup}<style id="nano-css">${nano.raw}</style>`;
}

The render server css path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Reuse a server-generated style element hydrate-server-css

import { create } from 'nano-css';
import { addon as addonHydrate } from 'nano-css/addon/hydrate';

const style = document.getElementById('nano-css');
const nano = create({ sh: style, pfx: 'acme-' });
addonHydrate(nano);

The hydrate server css path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Apply vendor prefixes to emitted declarations prefix-browser-properties

import { create } from 'nano-css';
import { addon as addonPrefixer } from 'nano-css/addon/prefixer';

const nano = create();
addonPrefixer(nano);
nano.put('.layout', { display: 'flex', userSelect: 'none' });

The prefix browser properties path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Convert a renderer to RTL output flip-right-to-left

import { create } from 'nano-css';
import { addon as addonRtl } from 'nano-css/addon/rtl';

const rtlNano = create({ pfx: 'rtl-' });
addonRtl(rtlNano);
rtlNano.put('.card', { marginLeft: '12px', textAlign: 'left' });

The flip right to left path depends on the matching nano-css addon being installed before use. Server and browser renderers must use the same addon order.

Alternatives

PackageRegistryPick it when
goobernpmUse it for a small generated-class API with less addon assembly
@emotion/cssnpmUse it for a mature runtime class API with broader composition and SSR documentation
styled-componentsnpmUse it when a React component styling model is preferred over renderer construction
@vanilla-extract/cssnpmUse it when styles should be typed and extracted during the build

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.