Plumeria

Introduction

Plumeria is a compile-time CSS-in-JS library for React. You write style objects in TypeScript, and the compiler turns them into static atomic CSS — while removing every trace of the library from your JavaScript bundle. No runtime code ships, no styles are computed in the browser.

Plumeria(Type)CompilationStatic Atomic CSS\mathtt{Plumeria(Type)} \xrightarrow{\text{Compilation}} \mathtt{Static\ Atomic\ CSS}

Usage

Styling is straightforward: define styles once with css.create(), and the compiler handles everything else. The atomic class names are determined at that point — all that remains is to attach them via the styleName prop.

import * as css from "@plumeria/core";

const styles = css.create({
  text: {
    fontSize: 16,
  },
});

export const Footer = () => {
  return <div styleName={styles.text}>MyText</div>;
};

Styles compile to zero across file boundaries, too. You can colocate styles with components or share them across files — structure them to fit your project. The only requirement is that every file using Plumeria imports @plumeria/core, which is how the compiler finds it.

styles.ts
import * as css from "@plumeria/core";

export const styles = css.create({
  common: {
    color: "gray",
  },
});
Component.tsx
import "@plumeria/core";
import { styles } from "./styles";

export const Component = () => {
  return <p styleName={styles.common}>Shared style</p>;
};

Specificity

The styleName prop accepts arrays, ternaries, and conditional expressions. Merging follows one rule: the right side wins. If isActive is true, the properties of active override the properties of base — always.

Component.tsx
const styles = css.create({
  base: {
    color: "gray",
    fontSize: 16,
  },
  active: {
    color: "blue",
  },
});

styleName={[styles.base, isActive && styles.active]}

Because the compiler controls the merge, the cascade specificity problem disappears entirely. Writing the merge as an operation, where \oplus combines styles left to right, disjoint styles simply concatenate:

parse(ABC)"classA classB classC"parse(A \oplus B \oplus C) \to \text{"classA classB classC"}

And when two styles set the same property, the losing atom on the left is filtered out at compile time — it never reaches the output:

parse(AcolorBcolor)"classB"parse(A_{color} \oplus B_{color}) \to \text{"classB"}

Types all the way down

Styling shouldn't require a runtime. Plumeria treats styles as type schemas rather than runtime objects — in fact, the package's only entry point is a .d.ts file. There is no JavaScript to import:

package.json
{
  "name": "@plumeria/core",
  "exports": {
    ".": {
      "types": "./lib/css.d.ts",
    },
  },
}

Static styles resolve to class names entirely at compile time. The only dynamic pieces — function keys and variant props — compile down to CSS variables, so even they leave no JavaScript behind. Every API compiles to nothing but static class names and CSS:

  • No runtime bundle
  • No runtime evaluation
  • No runtime parsing
  • No runtime dependencies

Of course, "zero-runtime" doesn't claim the cost vanishes — it claims the cost moves. All of the work happens at build time, and the browser only ever receives class names and CSS.


To learn what motivated this design, read Why Plumeria?. For integration with bundlers and the full API, see the API reference and Installation.

On this page