Vue
Subscribe Vue components to resolved GenomeJS traits with reactive refs.
The Vue adapter exposes a resolved GenomeJS value as a Vue ref.
const color = useGenomeTrait(genome, "color");The ref updates whenever the Genome mutates.
Installation
npm install @genomejs/core @genomejs/vueThe package requires Vue 3 or newer.
Export
import { useGenomeTrait } from "@genomejs/vue";Function signature
useGenomeTrait(
genome: Genome,
name: string,
): Ref<Primitive>Parameters
| Parameter | Type | Description |
|---|---|---|
genome | Genome | Genome instance to observe |
name | string | Primitive or derived token name |
Return value
Ref<string | number>;The ref initially contains the current resolved trait.
Create a shared Genome
Create src/lib/theme-genome.ts:
import { Genome } from "@genomejs/core";
export const themeGenome = new Genome({
primitives: {
lightSurface: "#ffffff",
darkSurface: "#121620",
lightForeground: "#171923",
darkForeground: "#f7f8fb",
baseSpacing: 16,
},
tokens: {
surface: (dna, context) =>
context.mode === "dark" ? dna.darkSurface : dna.lightSurface,
foreground: (dna, context) =>
context.mode === "dark" ? dna.darkForeground : dna.lightForeground,
spacing: (dna, context) => {
const scale = typeof context.scale === "number" ? context.scale : 1;
return `${Number(dna.baseSpacing) * scale}px`;
},
},
});
themeGenome.mutate({
mode: "light",
scale: 1,
});Use a trait in a component
<script setup lang="ts">
import { useGenomeTrait } from "@genomejs/vue";
import { themeGenome } from "@/lib/theme-genome";
const surface = useGenomeTrait(themeGenome, "surface");
const foreground = useGenomeTrait(themeGenome, "foreground");
const spacing = useGenomeTrait(themeGenome, "spacing");
</script>
<template>
<section
:style="{
display: 'grid',
gap: spacing,
padding: spacing,
backgroundColor: surface,
color: foreground,
}"
>
<h2>Reactive theme</h2>
<p>
Current spacing:
{{ spacing }}
</p>
</section>
</template>Vue automatically unwraps refs in templates.
In JavaScript or TypeScript code, read the ref through .value:
console.log(spacing.value);Mutate from Vue
<script setup lang="ts">
import { useGenomeTrait } from "@genomejs/vue";
import { themeGenome } from "@/lib/theme-genome";
const surface = useGenomeTrait(themeGenome, "surface");
function useLightMode() {
themeGenome.mutate({
mode: "light",
});
}
function useDarkMode() {
themeGenome.mutate({
mode: "dark",
});
}
</script>
<template>
<div>
<p>
Current surface:
{{ surface }}
</p>
<button type="button" @click="useLightMode">Light</button>
<button type="button" @click="useDarkMode">Dark</button>
</div>
</template>Lifecycle behavior
The adapter:
- Reads the current value when
useGenomeTrait()runs - Registers a Genome subscription when the component mounts
- Updates the Vue ref after Genome mutations
- Unsubscribes when the component unmounts
Conceptually:
setup()
↓
ref(genome.getTrait(name))
↓
component mounts
↓
genome.subscribe()
↓
genome.mutate()
↓
ref.value updates
↓
Vue updates the template
↓
component unmounts
↓
unsubscribe()You do not need to manage the subscription manually.
Use the returned ref in script
const spacing = useGenomeTrait(genome, "spacing");
watchEffect(() => {
console.log(spacing.value);
});The value can be used with other Vue reactive APIs because it is a normal ref.
Reading multiple traits
const surface = useGenomeTrait(genome, "surface");
const foreground = useGenomeTrait(genome, "foreground");The current adapter observes one trait per function call.
Keep the Genome stable
Avoid constructing a new Genome inside reactive work that may run repeatedly.
Prefer a shared instance:
export const genome = new Genome(config);Or create one stable component-local instance during setup:
const genome = new Genome(config);A top-level <script setup> statement runs once for each component instance, not on every reactive update.
CSS custom properties
Use the generated --g-* values directly when JavaScript access is unnecessary:
<template>
<article class="card">Styled by GenomeJS</article>
</template>
<style scoped>
.card {
color: var(--g-foreground);
background: var(--g-surface);
gap: var(--g-spacing);
}
</style>Use useGenomeTrait() when:
- The value appears in rendered text
- Component logic depends on the value
- Another Vue reactive computation needs it
Browser bindings
Browser state can mutate the Genome from Vue lifecycle hooks:
<script setup lang="ts">
import { onMounted, onUnmounted } from "vue";
import { themeGenome } from "@/lib/theme-genome";
let query: MediaQueryList | undefined;
function updateMode() {
if (!query) {
return;
}
themeGenome.mutate({
mode: query.matches ? "dark" : "light",
});
}
onMounted(() => {
query = window.matchMedia("(prefers-color-scheme: dark)");
updateMode();
query.addEventListener("change", updateMode);
});
onUnmounted(() => {
query?.removeEventListener("change", updateMode);
});
</script>Unknown trait behavior
The adapter initializes its ref by calling:
genome.getTrait(name);An unknown name throws:
useGenomeTrait(genome, "missingTrait");Current error:
Unknown token: "missingTrait"Server rendering
The initial ref value is read synchronously from the Genome.
The subscription itself begins in onMounted(), so browser updates start after the component mounts.
Keep initial Genome context consistent between server and browser rendering to avoid rendering different first values.
Common problems
The ref does not update
Confirm that:
- The component uses the same Genome instance that is being mutated.
- The component has mounted.
- The requested trait name exists.
- The Genome mutation completes without throwing.
Reading the ref in script shows an object
Use:
spacing.value;Vue only auto-unwraps refs automatically in templates.
CSS variables appear on the wrong element
Pass the intended target to the Core constructor or use scope() for a component-specific target.
Notes
- The adapter returns
Ref<Primitive>. - The initial value is read immediately.
- Subscription begins when the component mounts.
- Cleanup occurs when the component unmounts.
- Unknown trait names throw.
- Vue 3 or newer is required.