GenomeJS
Core

subscribe()

Run a listener after GenomeJS resolves mutations and expresses updated values.

subscribe() registers a listener that runs whenever the Genome finishes a resolution cycle.

Signature

genome.subscribe(
  listener: () => void,
): () => void

Parameters

ParameterTypeDescription
listener() => voidFunction called after the Genome resolves and expresses values

Return value

The method returns an unsubscribe function:

() => void

Call it to remove the listener.

Basic example

const unsubscribe = genome.subscribe(() => {
  console.log("Genome updated");
});

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

unsubscribe();

After unsubscribe() is called, later mutations no longer invoke that listener.

Reading values inside a listener

The listener receives no arguments.

Use getTrait() to read the latest resolved values:

const unsubscribe = genome.subscribe(() => {
  const surface = genome.getTrait("surface");

  const foreground = genome.getTrait("foreground");

  console.log({
    surface,
    foreground,
  });
});

Notification order

During mutation, GenomeJS performs these operations synchronously:

1. Merge context
2. Resolve derived values
3. Express changed CSS properties
4. Notify subscribers

This means a listener can read the latest values and inspect the updated CSS output.

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

  console.log(document.documentElement.style.getPropertyValue("--g-spacing"));
});

Subscribers run after every resolution

A subscriber is notified after a mutation even when the final CSS values are unchanged.

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

If the resolved value remains the same:

  • GenomeJS skips the identical CSS write.
  • Subscribers are still notified.

A subscriber that only cares about one token can compare the previous and next values.

let previous = genome.getTrait("spacing");

const unsubscribe = genome.subscribe(() => {
  const next = genome.getTrait("spacing");

  if (next === previous) {
    return;
  }

  previous = next;

  console.log("Spacing changed:", next);
});

Multiple subscribers

const unsubscribeLogger = genome.subscribe(() => {
  console.log("Updated");
});

const unsubscribeRenderer = genome.subscribe(() => {
  renderPreview();
});

Both listeners run after resolution.

Clean them up independently:

unsubscribeLogger();
unsubscribeRenderer();

Browser event integration

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

const unsubscribe = genome.subscribe(() => {
  if (!output) {
    return;
  }

  output.textContent = String(genome.getTrait("spacing"));
});

React integration

Most React components should use useGenomeTrait() rather than calling subscribe() manually.

const spacing = useGenomeTrait(genome, "spacing");

The React adapter connects the subscription to useSyncExternalStore.

Manual subscription is still useful for:

  • Logging
  • Imperative integrations
  • Non-React code
  • Updating third-party libraries
  • Coordinating external stores

Vue integration

The Vue adapter subscribes when the component mounts and cleans up when it unmounts:

const spacing = useGenomeTrait(genome, "spacing");

It returns a Vue ref.

Svelte integration

The Svelte adapter uses Genome subscriptions inside a rune-aware reactive wrapper:

const spacing = genomeTrait(genome, "spacing");

Read its current value with:

spacing.value;

Cleanup

Always keep and call the returned cleanup function for manual subscriptions.

const unsubscribe = genome.subscribe(listener);

// Later
unsubscribe();

Failing to unsubscribe may keep unused listeners registered for the lifetime of the Genome instance.

Listener errors

GenomeJS does not currently catch errors thrown by listeners.

genome.subscribe(() => {
  throw new Error("Subscriber failed");
});

That error propagates from the mutation or resolution call that triggered the listener.

Keep listeners small and handle expected failures inside them.

genome.subscribe(() => {
  try {
    synchronizeExternalSystem();
  } catch (error) {
    console.error(error);
  }
});

Notes

  • Listeners receive no arguments.
  • Listeners run synchronously.
  • Notifications occur after CSS expression.
  • A listener runs after every resolution cycle.
  • The returned function removes that listener.
  • Reading a trait does not automatically subscribe.
  • Framework adapters manage subscriptions for components.

On this page