npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@react-hive/honey-css

v1.15.0

Published

A lightweight CSS tokenizer and parser that produces a simple AST for custom CSS processing.

Readme

@react-hive/honey-css

A lightweight CSS tokenizer and structural parser that generates a minimal, predictable AST for custom CSS processing.

The honey-css is designed as a focused foundation for building:

  • CSS transformers
  • Lightweight preprocessors
  • CSS-in-JS compilers
  • Custom styling engines

All without pulling in heavyweight ecosystems like PostCSS.

It gives you structure — not policy.

✨ Why honey-css?

Modern CSS tooling is incredibly powerful — but often excessively complex.

Many parsers aim to support the entire CSS specification, plugin systems, validation layers, and edge-case semantics. That’s valuable — but sometimes unnecessary.

Often, what you actually need is:

  • A small tokenizer
  • A predictable AST
  • Nested rule support
  • Clean grammar boundaries
  • Composable parsing primitives

The honey-css focuses on the practical sweet spot:

  • 🍯 Minimal surface area
  • 🎯 Deterministic, structural output
  • 🌳 Small and easy-to-traverse AST
  • 🧩 Designed for composition and transformation
  • ⚡ Fast and dependency-light

It intentionally performs structural parsing only — not full CSS validation.
That makes it ideal for transformation pipelines where predictability and simplicity matter more than spec completeness.

If you're building your own styling layer, compiler, or design-system engine, honey-css gives you the core building blocks — without unnecessary overhead or abstraction.

✨ Features

  • ✅ Tokenizes raw CSS into a structured, readable token stream
  • ✅ Parses tokens into a minimal, predictable AST
  • ✅ Fully supports nested rules and nested at-rules
  • ✅ Correctly handles real-world CSS constructs:
    • Declarations (color: red;)
    • Complex selectors (.btn:hover, :is(...), attribute selectors)
    • At-rules (@media, @layer, @keyframes, @import, etc.)
    • Parameter groups (url(...), var(...), nested functions)
    • Quoted strings (content: "hello")
    • Block comments (/* ... */)
  • ✅ Distinguishes directive at-rules (body: null) from block at-rules
  • ✅ Resilient parsing with safe recovery (skips unknown or malformed tokens)
  • ✅ Deterministic output — no hidden transformations during parsing
  • ✅ Small surface area with composable low-level parsing utilities
  • ✅ Designed for transformation pipelines and CSS-in-JS compilers
  • ✅ Tiny, fast, dependency-light, and easy to extend

📦 Installation

Install with pnpm (recommended):

pnpm add @react-hive/honey-css

🚀 Quick Start

Tokenizing CSS

import { tokenizeCss } from "@react-hive/honey-css";

const tokens = tokenizeCss(`
  .btn {
    color: red;
    padding: 12px;
  }
`);

console.log(tokens);

Output:

[
  { "type": "text", "value": ".btn" },
  { "type": "braceOpen" },

  { "type": "text", "value": "color" },
  { "type": "colon" },
  { "type": "text", "value": "red" },
  { "type": "semicolon" },

  { "type": "text", "value": "padding" },
  { "type": "colon" },
  { "type": "text", "value": "12px" },
  { "type": "semicolon" },

  { "type": "braceClose" }
]

🧠 parseCss

Parses a full CSS string into a minimal Honey AST.

This is the high-level entry point that wires together:

  • tokenizeCss
  • createCssTokenCursor
  • parseCssNodes

It performs structural parsing only — it does not validate CSS identifiers or enforce full CSS specification rules. The goal is predictable AST generation suitable for transformations and CSS-in-JS engines.

What It Supports

At the root level, parseCss recognizes:

  • Declarations → color: red;
  • Rules → .btn { ... }
  • At-rules → @media (...) { ... }
  • Directive at-rules → @import url("x.css");
  • Nested structures inside rules and at-rules

Stray semicolons and unknown tokens are skipped safely.

Recovery Behavior

The parseCss is designed to be resilient:

  • Unknown tokens are skipped
  • Malformed constructs do not throw
  • Parsing continues whenever possible
  • Infinite loops are prevented via forward progress

This makes it ideal for transformation pipelines where partial input may occur.

Example

import { parseCss } from "@react-hive/honey-css";

const ast = parseCss(`
  color: red;

  .btn {
    padding: 8px;

    @media (max-width: 600px) {
      display: none;
    }
  }
`);

console.log(ast);

Output:

{
  "type": "stylesheet",
  "body": [
    { "type": "declaration", "prop": "color", "value": "red" },
    {
      "type": "rule",
      "selector": ".btn",
      "body": [
        { "type": "declaration", "prop": "padding", "value": "8px" },
        {
          "type": "atRule",
          "name": "media",
          "params": "(max-width: 600px)",
          "body": [
            { "type": "declaration", "prop": "display", "value": "none" }
          ]
        }
      ]
    }
  ]
}

How It Works Internally

The parseCss delegates all grammar handling to:

parseCssNodes(cursor, { stopAtBraceClose: false })

This means:

  • Root-level and block-level parsing share the same engine
  • Grammar logic lives in one place
  • The parser stays small and composable

When to Use It

Use parseCss when:

  • You want a complete AST for transformation
  • You are building a CSS-in-JS engine
  • You are implementing custom at-rule processors
  • You need a structured CSS without heavy dependencies

If you need finer control, you can directly use:

  • parseCssNodes
  • parseCssBlock
  • parseCssRule
  • parseCssAtRule

🧭 Token Cursor Utilities

For writing your own parser logic, transformers, or custom readers, honey-css provides a small helper:

createCssTokenCursor

The cursor is a lightweight abstraction over the token stream that enables:

  • lookahead (peek)
  • sequential reading (next)
  • safe assertions (expect)
  • speculative parsing (mark + reset)
  • reading chunks (readUntil)
  • skipping tokens for recovery (skipUntil)

Cursor API

The createCssTokenCursor(tokens) returns an object with:

  • peek() - Look at the current token without consuming it
  • next() - Consume the current token and advance
  • isEof() - Returns true when the token stream is finished
  • expect(type) - Assert the next token type (throws if mismatch)
  • mark()/reset(mark) - Create checkpoints for speculative parsing
  • readUntil([...]) - Read combined text/string/params until a stop token
  • skipUntil([...]) - Skip tokens until a stop token is found

Example: Reading a Declaration Manually

import { tokenizeCss, createCssTokenCursor } from "@react-hive/honey-css";

const tokens = tokenizeCss('color: red;');
const cursor = createCssTokenCursor(tokens);

// Read property name
const prop = cursor.readUntil(['colon']);
cursor.expect('colon');

// Read value
const value = cursor.readUntil(['semicolon']);
cursor.expect('semicolon');

console.log(prop);  // "color"
console.log(value); // "red"

Example: Skipping Until a Block Ends

cursor.skipUntil(["braceClose"]);
cursor.expect("braceClose");

This is extremely useful for:

  • parser error recovery
  • ignoring unsupported syntax
  • skipping unknown nested blocks

🔎 readCssSelector

When building custom rule parsing logic, you often need to read a selector safely until {.

The readCssSelector reconstructs the selector from tokens while preserving:

  • pseudo selectors (:hover)
  • pseudo elements (::before)
  • attribute selectors ([data-id="x"])
  • combinators (>, +, ~)
  • pseudo functions (:not(...), :nth-child(...))

It stops before consuming the { token.

Example

import {
  tokenizeCss,
  createCssTokenCursor,
  readCssSelector,
} from "@react-hive/honey-css";

const tokens = tokenizeCss(`
  button:not(:disabled):hover {
    opacity: 0.5;
  }
`);

const cursor = createCssTokenCursor(tokens);

const selector = readCssSelector(cursor);
cursor.expect("braceOpen");

console.log(selector);
// "button:not(:disabled):hover"

🔁 readCssKeyOrSelector

Inside a block, grammar becomes ambiguous:

selector { ... }
property: value;

To resolve this, use readCssKeyOrSelector.

It:

  • Speculatively parses a selector
  • Accepts it only if { follows
  • Otherwise, rewinds and reads a declaration key

Example

import {
  tokenizeCss,
  createCssTokenCursor,
  readCssKeyOrSelector,
} from "@react-hive/honey-css";

const tokens = tokenizeCss(`
  .btn { color: red; }
`);

const cursor = createCssTokenCursor(tokens);

const keyOrSelector = readCssKeyOrSelector(cursor);

console.log(keyOrSelector); // ".btn"

🧱 parseCssDeclaration

When building a custom parser on top of the token cursor, you often need to parse a single declaration inside a rule block.

The parseCssDeclaration handles this safely and consistently.

It:

  • Expects the cursor to be positioned at the : token
  • Reads the value until ; or }
  • Supports missing trailing semicolons
  • Preserves strings and nested params like var(...) or url(...)

Example

import {
  tokenizeCss,
  createCssTokenCursor,
  parseCssDeclaration,
} from "@react-hive/honey-css";

const tokens = tokenizeCss(`
  color: var(--primary, red);
`);

const cursor = createCssTokenCursor(tokens);

// Read property name
const prop = cursor.readUntil(["colon"]);

// Parse declaration
const declaration = parseCssDeclaration(cursor, prop);

console.log(declaration);

Output:

{
  type: "declaration",
  prop: "color",
  value: "var(--primary, red)"
}

🧩 resolveCssSelector

When implementing nested rules (like in CSS-in-JS engines), child selectors must be resolved against their parent selector.

The resolveCssSelector helper performs this safely and predictably.

It:

  • Performs full Cartesian expansion of comma-separated selector lists
  • Replaces explicit parent references (&)
  • Creates descendant relationships when & is not present
  • Preserves complex selectors, including:
    • Pseudo-classes (:hover)
    • Pseudo-elements (::before)
    • Attribute selectors ([data-x="a,b"])
    • Nested selector functions (:is(...), :not(...))
    • Combinators (>, +, ~)

Basic Example

import { resolveCssSelector } from "@react-hive/honey-css";

resolveCssSelector(".child", ".scope");
// → ".scope .child"

resolveCssSelector("&:hover", ".btn");
// → ".btn:hover"

Comma Expansion

Both parent and child selectors may contain comma-separated lists.

resolveCssSelector(".a, .b", ".scope");
// → ".scope .a, .scope .b"

resolveCssSelector(".x, .y", ".a, .b");
// → ".a .x, .a .y, .b .x, .b .y"

Parent Reference (&)

If the child selector contains &, it is replaced with the parent selector.

resolveCssSelector("& + &", ".item");
// → ".item + .item"

resolveCssSelector("&:hover, .icon", ".btn, .card");
// → ".btn:hover, .btn .icon, .card:hover, .card .icon"

Complex Selectors

The resolver safely handles nested commas inside functions and attribute selectors.

resolveCssSelector(':is(.a, .b)', '.scope');
// → ".scope :is(.a, .b)"

resolveCssSelector('[data-x="a,b"]', '.scope');
// → ".scope [data-x=\"a,b\"]"

🧾 stringifyCss

After transforming or generating a CSS AST, you can convert it back into a compact CSS string using stringifyCss.

This is the final stage of the honey-css pipeline.

What It Does:

  • Converts the AST back into valid compact CSS
  • Removes empty declarations (value.trim() === "")
  • Removes empty rules (rule body becomes empty after stringification)
  • Removes empty block at-rules (at-rules with body: [] that stringify to nothing)
  • Preserves directive at-rules (body: null) and prints them with ;

Example

import { stringifyCss } from "@react-hive/honey-css";

const ast = {
  type: "stylesheet",
  body: [
    {
      type: "rule",
      selector: ".btn",
      body: [
        { type: "declaration", prop: "padding", value: "8px" },
        { type: "declaration", prop: "color", value: "red" },
      ],
    },
  ],
};

const css = stringifyCss(ast);

console.log(css);
// ".btn{padding:8px;color:red;}"

Directive VS block at-rules

The stringifyCss treats at-rules with body === null as directive-style rules and serializes them with a trailing semicolon.

import { stringifyCss } from "@react-hive/honey-css";

const ast = {
  type: "stylesheet",
  body: [
    {
      type: "atRule",
      name: "charset",
      params: '"UTF-8"',
      body: null,
    },
  ],
};

console.log(stringifyCss(ast));
// '@charset "UTF-8";'

Block at-rules (body !== null) are serialized using curly braces: @name params{...}.

import { stringifyCss } from "@react-hive/honey-css";

const ast = {
  type: "stylesheet",
  body: [
    {
      type: "atRule",
      name: "media",
      params: "(min-width: 100px)",
      body: [],
    },
  ],
};

console.log(stringifyCss(ast));
// ""

🧱 Low-Level Block & Rule Parsers

When building your own parser pipeline on top of createCssTokenCursor, you can use the lower-level block and rule utilities directly.

🧩 parseCssAtRule

Parses an at-rule from the token stream.

Supports both block-style and directive-style at-rules:

@media (max-width: 768px) { ... }
@keyframes spin { ... }
@import url("file.css");

What is does:

  • Merges space-delimited and params tokens correctly
  • Returns body: null for directive at-rules
  • Returns body: [] for empty block at-rules

Example

import {
  tokenizeCss,
  createCssTokenCursor,
  parseCssAtRule,
} from "@react-hive/honey-css";

const tokens = tokenizeCss(`
  @media (max-width: 768px) {
    color: red;
  }
`);

const cursor = createCssTokenCursor(tokens);

const atRule = parseCssAtRule(cursor);

console.log(atRule);

Output:

{
  type: "atRule",
  name: "media",
  params: "(max-width: 768px)",
  body: [
    {
      type: "declaration",
      prop: "color",
      value: "red"
    }
  ]
}

🧱 parseCssBlock

Parses the contents of a { ... } block.

Resolves grammar ambiguity inside blocks:

selector { ... }
property: value;
@rule ...

It:

  • Stops at the matching }
  • Skips stray semicolons (;) safely
  • Delegates:
    • declarations → parseCssDeclaration
    • nested rules → parseCssRule
    • nested at-rules → parseCssAtRule

Example

import {
  tokenizeCss,
  createCssTokenCursor,
} from "@react-hive/honey-css";

import { parseCssBlock } from "@react-hive/honey-css";

const tokens = tokenizeCss(`
  {
    color: red;
    .child { padding: 8px; }
  }
`);

const cursor = createCssTokenCursor(tokens);
cursor.expect("braceOpen");

const nodes = parseCssBlock(cursor);

console.log(nodes);

Output:

[
  { type: "declaration", prop: "color", value: "red" },
  {
    type: "rule",
    selector: ".child",
    body: [
      { type: "declaration", prop: "padding", value: "8px" }
    ]
  }
]

🧾 parseCssRule

Parses a CSS rule body for a given selector.

  • Expects the cursor to be positioned at {
  • Delegates body parsing to parseCssBlock
  • Preserves the selector exactly as provided

Example

import {
  tokenizeCss,
  createCssTokenCursor,
} from "@react-hive/honey-css";

import { parseCssRule } from "@react-hive/honey-css";

const tokens = tokenizeCss(`
  {
    color: red;
  }
`);

const cursor = createCssTokenCursor(tokens);

const rule = parseCssRule(cursor, ".btn");

console.log(rule);

Output:

{
  type: "rule",
  selector: ".btn",
  body: [
    { type: "declaration", prop: "color", value: "red" }
  ]
}

🌳 AST Overview

The AST is intentionally minimal and easy to traverse.

Stylesheet Root

{
  type: "stylesheet",
  body: HoneyCssNode[]
}

Declaration Node

{
  type: "declaration",
  prop: "padding",
  value: "12px"
}

Represents:

padding: 12px;

Rule Node

{
  type: "rule",
  selector: ".child:hover",
  body: [...]
}

Represents:

.child:hover {
  opacity: 0.5;
}

At-Rule Node

{
  type: "atRule",
  name: "media",
  params: "(max-width: 768px)",
  body: [...]
}

Represents:

@media (max-width: 768px) {
  color: red;
}

🎯 Use Cases

The honey-css intentionally exposes low-level parsing primitives so you can build exactly the tooling you need — without inheriting a large opinionated ecosystem.

It is a foundation, not a framework.

You can use it to:

  • 🛠 Build your own CSS compiler or transformer pipeline
  • 🧩 Implement custom at-rules (e.g. @honey-media, @theme, etc.)
  • 🔁 Flatten nested rules for CSS-in-JS engines
  • 🎨 Create design-system or token processors
  • 🔍 Perform AST transformations before stringification
  • 🧠 Build domain-specific styling languages on top of CSS
  • 📦 Implement scoped or isolated CSS engines
  • ⚡ Generate minimal production-ready CSS from structured input

Because the parser is structural (not validating), it is predictable and easy to control.
There are no hidden behaviors — transformations are explicit and composable.

What honey-css Is Not

  • ❌ Not a full CSS spec implementation
  • ❌ Not a PostCSS replacement
  • ❌ Not a validation engine
  • ❌ Not a plugin-heavy ecosystem

Instead, it is a focused, lightweight foundation for people who want to build their own styling engines — cleanly, deterministically, and without unnecessary abstraction.

📄 License

MIT © Mike Aliinyk

Part of the React Hive ecosystem 🐝