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:
- Primitives — raw string and number values
- Tokens — functions that derive new values
- Context — runtime conditions that may change
- DNA — the current flat object of resolved values
- A target — the element that receives generated CSS custom properties
Primitives ───────┐
├──→ Token functions ──→ Resolved DNA
Other tokens ─────┤ │
│ ├──→ CSS custom properties
Runtime context ──┘ └──→ SubscribersPrimitives
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;dnaprovides primitives and previously resolved tokens.contextprovides 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
↓
sectionSpacingIt 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");
// 12Framework 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:
- Resolves all derived token functions
- Stores the new DNA values
- Expresses changed values as CSS custom properties
- 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-radiusUse 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.
Recommended mental model
Think of GenomeJS as a small reactive compiler:
Configuration
↓
Dependency discovery
↓
Graph validation and ordering
↓
Value resolution
↓
CSS output and subscriptionsCurrent constraints
For the current package version:
- Primitive values are strings or numbers.
- Static values should be placed under
primitives. - Entries under
tokensshould be functions. - Context values are application-defined and should be validated when necessary.
- Dependency discovery tracks
dnaproperty reads. - A context mutation causes derived tokens to resolve again.