mrkeyoor.com_
Sat 08 Aug 21:58 UTC
npmWeb Frontendupdated 08 Aug 2026

nano-css

nano-css is a modular runtime CSS-in-JS renderer. The core create() function gives you put(), which converts JavaScript style objects into CSS rules and inserts them into a browser style sheet or accumulates them as a string on the server. Generated classes, style sheets, React components, nesting, keyframes, prefixing, right-to-left conversion, hydration, and extraction all come from separate addons or presets. That makes the core small, but a real application must choose and initialize a styling stack.

Verdict

nano-css earns its name at the core, but the useful product is an addon graph you own and test. Keep it for existing v5 systems or unusually custom renderers; new React projects usually get a clearer maintenance story from goober, Emotion, or a build-time option.

API stability4/5The current major has remained v5 since March 2019, and its central contract is still a renderer object whose methods are extended by addon functions. Core put(), plus the rule and sheet patterns, have had years to settle. Stability is less certain at the edges because dozens of addons wrap and replace renderer methods, their ordering can affect behavior, and some public entry points lack the same TypeScript coverage as the core and sheet preset.
Docs3/5The project has a substantial reference with individual pages for installation, presets, each addon, SSR, hydration, and extraction. Important caveats are present, including leading spaces from rule(), lazy sheet injection, stable hashing for SSR, and incomplete hydration. The weak points are age and context: the size comparison targets version 1.15.3, many examples predate current React conventions, extraction is described as a primitive, and setup consequences such as wildcard React peers are not explained.
Maintenance3/5The repository was pushed on February 16, 2026 and is not archived, while npm version 5.6.2 shipped on July 20, 2024. The project still receives dependency and repository work, but feature-release cadence is slow and the published development stack centers on React 17, Storybook 6.5, Jest 26, and TypeScript 4.9. The repository also reports 33 open issues and pull requests, a meaningful queue for a project with one primary maintainer footprint.
Ecosystem4/5The registry recorded 3,420,596 downloads in the measured week, and the package ships a broad catalog of addons for React, virtual DOM libraries, SSR, atomic styles, prefixing, RTL conversion, animations, resets, source maps, and CSSOM work. It is still a smaller direct community than Emotion or styled-components: the repository has 446 stars, integrations are mostly maintained inside this one package, and the preset model has limited third-party tooling around it.

Use it if

  • You want runtime CSS-in-JS without wrapper components and prefer direct CSS rule insertion over inline style objects
  • You need a renderer that can work without React and are willing to install only the addons your application uses
  • You want stable generated class names, server-side CSS collection, and client hydration from one low-level system
  • You are maintaining an existing nano-css v5 codebase and need its rule, sheet, jsx, styled, atoms, or RTL APIs
Skip it if

Setup reality

npm install nano-css does not give you the README's full feature list. The core exposes create() and preinstalls only put(); common tasks such as generated classes, named sheets, stable hashing, advanced nesting, keyframes, React elements, prefixing, SSR hydration, and extraction are separate addons. The sheet preset installs stable, nesting, atoms, keyframes, rule, sheet, and development source maps. The React preset installs a much larger set and uses React.createElement. Although the core describes itself as framework-agnostic, the published v5.6.2 metadata declares non-optional react and react-dom peer dependencies with wildcard versions, which can surprise standalone consumers under modern npm peer installation. Browser use appends a style element to document.head unless you provide one through the sh option; production uses insertRule(), while development writes readable text nodes and creates an extra test sheet. Set NODE_ENV correctly or production behavior and size will differ. For SSR, create a renderer for the request, execute every lazy sheet rule you need, embed nano.raw, install stable naming, and add the hydrate addon on the client with the matching style element. Reusing one server renderer keeps accumulating raw CSS. Hydration does not cover media queries or keyframes. Addons wrap renderer methods, so installation order and documented dependencies matter. The extract addon is only a low-level primitive, not a complete bundler integration, and TypeScript coverage varies across addon entry points.

Patterns

Create a renderer and inject a selectorinject-global-rule

import { create } from 'nano-css';

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

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

put() is the only styling method installed by the core package. In a browser it creates a style element in document.head unless you pass one as sh.

Generate a class from a style objectgenerate-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' });

rule() returns a class name with a leading space so it can be concatenated. Install stable before rule when server and client must hash styles identically.

Define several lazily inserted classescreate-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;

sheet() does not insert a rule until its property is accessed. Touch every class needed by an SSR response before reading nano.raw.

Use parent references and grouped selectorsnest-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 ampersand and comma interpolation shown here come from the nesting addon included by the sheet preset, not from bare create() alone.

Generate a responsive classadd-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');

Server output can contain media queries, but the hydrate addon does not recognize media-query rules as already present on the client.

Create a uniquely named animationcreate-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 keyframes addon emits prefixed variants by default. Its rules are another documented gap in client hydration.

Build a prop-driven React buttoncreate-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 React preset installs many addons and uses React.createElement. Semantic props such as danger can reach the DOM unless your component layer filters them.

Apply one-off styles through a styling blockoverride-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 css prop is consumed by nano-css and is not passed to the element. The $as and $ref props are also reserved by the jsx addon.

Collect CSS during server renderingrender-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>`;
}

Create an isolated renderer per request or reset it deliberately; raw is an accumulating string. Escape or control all generated HTML values in real templates.

Reuse a server-generated style elementhydrate-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);

Use the same prefix, style definitions, and stable naming on both sides. Existing media queries and keyframes are not hydrated by this addon.

Add vendor prefixes to emitted declarationsprefix-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' });

Prefixing is not part of the base renderer. This addon uses inline-style-prefixer, one of nano-css v5's runtime dependencies.

Convert a renderer to RTL outputflip-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 RTL addon wraps put() and transforms every style object through rtl-css-js. Use a separate renderer if the same page emits both LTR and RTL rules.

Alternatives

PackageRegistryPick it when
goobernpmYou want a small CSS-in-JS API with less addon assembly and straightforward React or framework integration
@emotion/cssnpmYou want a mature generated-class API, composition, SSR support, and a larger documentation ecosystem
styled-componentsnpmYour React team prefers component-scoped styles and accepts a larger runtime abstraction
@vanilla-extract/cssnpmYou want typed styles extracted at build time with no runtime style injection