GenomeJS

Core Concepts

Understand primitives, derived tokens, runtime context, the DNA object, mutation, and CSS expression.

GenomeJS turns a collection of values and relationships into a reactive token system.

A typical configuration contains:

  1. Primitives — raw string and number values
  2. Tokens — functions that derive new values
  3. Context — runtime conditions that may change
  4. DNA — the current flat object of resolved values
  5. A target — the element that receives generated CSS custom properties
Primitives ───────┐
                  ├──→ Token functions ──→ Resolved DNA
Other tokens ─────┤                           │
                  │                           ├──→ CSS custom properties
Runtime context ──┘                           └──→ Subscribers

Primitives

Primitives are the raw inputs of a Genome.

const primitives = {
  baseSpacing: 16,
  brandColor: "#7c6cff",
};

Primitive values may currently be strings or numbers.

They are copied directly into the resolved DNA object and expressed as CSS custom properties.

--g-base-spacing: 16;
--g-brand-color: #7c6cff;

Use primitives for values that do not depend on other values or runtime context.

Tokens

Tokens are functions that calculate derived values.

const tokens = {
  spacing: (dna, context) =>
    `${Number(dna.baseSpacing) * Number(context.scale ?? 1)}px`,
};

A token receives two arguments:

(dna, context) => value;
  • dna provides primitives and previously resolved tokens.
  • context provides runtime state.

Token relationships are discovered from property reads on dna.

const tokens = {
  controlSpacing: (dna) => Number(dna.baseSpacing) * 0.75,

  sectionSpacing: (dna) => Number(dna.controlSpacing) * 4,
};

GenomeJS discovers this chain:

baseSpacing

controlSpacing

sectionSpacing

It then finds an order in which those values can be resolved safely.

Runtime context

Context stores environmental or application state that may change while the application runs.

genome.mutate({
  mode: "dark",
  scale: 1.25,
  density: "compact",
});

Context can contain any property names your token functions understand.

Typical examples include:

  • Color mode
  • Contrast preference
  • Density
  • Viewport category
  • Container width
  • User-selected scale
  • Input method
  • Application state

The Core package does not enforce a context schema. Your application should validate or narrow values when a token expects a particular type.

const scale = typeof context.scale === "number" ? context.scale : 1;

The DNA object

DNA is the flat object containing the Genome’s currently resolved values.

Given:

const genome = new Genome({
  primitives: {
    spacing: 16,
  },

  tokens: {
    compactSpacing: (dna) => Number(dna.spacing) * 0.75,
  },
});

The resolved DNA is conceptually:

{
  spacing: 16,
  compactSpacing: 12,
}

The full DNA object is internal, but individual values are available through getTrait():

genome.getTrait("compactSpacing");
// 12

Framework adapters expose the same values through their framework’s reactive APIs.

Mutation

mutate() shallowly merges a context patch into the current context.

genome.mutate({
  scale: 1.25,
});

A later mutation can change one property without repeating the others:

genome.mutate({
  mode: "dark",
});

Conceptually, the context becomes:

{
  scale: 1.25,
  mode: "dark",
}

After a mutation, GenomeJS:

  1. Resolves all derived token functions
  2. Stores the new DNA values
  3. Expresses changed values as CSS custom properties
  4. Notifies subscribers

CSS expression

Resolved values are expressed using the --g- prefix.

Camel-cased token names are converted to kebab case:

buttonColor   → --g-button-color
sectionGap    → --g-section-gap
baseRadius    → --g-base-radius

Use the generated values in ordinary CSS:

.card {
  gap: var(--g-section-gap);
  border-radius: var(--g-base-radius);
}

GenomeJS skips a CSS write when the newly resolved string value is identical to the last expressed value.

Subscriptions

Applications can subscribe directly to changes:

const unsubscribe = genome.subscribe(() => {
  console.log(genome.getTrait("sectionGap"));
});

Always call the returned cleanup function when the subscription is no longer needed:

unsubscribe();

React, Vue, and Svelte adapters manage this subscription lifecycle for components.

Scoped instances

A child Genome can express the same token system onto a different element with context overrides:

const panel = document.querySelector<HTMLElement>("[data-panel]");

if (panel) {
  const panelGenome = genome.scope(panel, {
    density: "compact",
  });
}

The parent and child have independent context and resolved output.

Think of GenomeJS as a small reactive compiler:

Configuration

Dependency discovery

Graph validation and ordering

Value resolution

CSS output and subscriptions

Current constraints

For the current package version:

  • Primitive values are strings or numbers.
  • Static values should be placed under primitives.
  • Entries under tokens should be functions.
  • Context values are application-defined and should be validated when necessary.
  • Dependency discovery tracks dna property reads.
  • A context mutation causes derived tokens to resolve again.

Next steps

On this page