INTRODUCTION

Introduction

What Plumeria is, what writing styles with it looks like, and how far its types reach.

Plumeria is a React styling library. You write style objects in TypeScript, and the compiler turns them into atomic CSS and rewrites every call site, so no library code reaches your JavaScript bundle and no style is computed in the browser.

Which value wins is never left to stylesheet order. Styles merge property by property and the one on the right wins, whether it comes from the same file, another module, or a parent through a prop, so splitting and moving files never changes what renders. A mistake stops the build at the line that caused it.

Write a style

Define styles with css.create() and attach them with the classStyle prop:

Footer.tsx
import * as css from '@plumeria/core';

const styles = css.create({
  text: {
    padding: 16,
    color: 'gray',
  },
});

export const Footer = () => <p classStyle={styles.text}>MyText</p>;

The import and the css.create() call compile away, and the declarations move to a generated stylesheet:

Output
export const Footer = () => <p className={"xqqbxt1d xuifcaas"}>MyText</p>;
Generated CSS
.xqqbxt1d { padding: 16px; }
.xuifcaas { color: gray; }

Compose from the right

classStyle accepts arrays, ternaries and conditions. When two styles set the same property, the one on the right wins:

Item.tsx
import * as css from '@plumeria/core';

const styles = css.create({
  base: {
    padding: 16,
    color: 'gray',
  },
  active: {
    color: 'teal',
  },
});

export const Item = ({ isActive }: { isActive: boolean }) => (
  <p classStyle={[styles.base, isActive && styles.active]}>MyText</p>
);

The class that loses never reaches the element:

Output
<p class="xqqbxt1d xuifcaas">MyText</p> <!-- isActive is false -->
<p class="xqqbxt1d xxcejlqg">MyText</p> <!-- isActive is true -->

A shorthand against a longhand is the exception: the longhand wins wherever the two meet, in either order. Specificity ranks every pair, and Composition laws states the merge formally.

Playground · Last style wins

Share across files

Styles compile to zero across file boundaries too, so you can keep them beside a component or share them, whichever fits the project:

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

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

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

A file that applies them with classStyle needs nothing else. A file that passes them to a component through any other prop imports @plumeria/core, which is how the compiler finds it; importing it in every file that uses Plumeria is always safe and keeps each file on the fastest path.

Types all the way down

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

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

Static styles resolve to class names entirely at compile time. The dynamic pieces leave no library behind either: function keys compile down to CSS variables, and a style chosen at runtime compiles to a literal lookup of precomputed class strings. What ships is class names, CSS, and the expressions you already wrote:

  • 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.

Next steps


This site is built with Plumeria. The documentation app lives in the same repository and depends on the workspace packages, so every release is exercised here — and every experiment is tried here — before it ships.

Edit on GitHub

On this page