Skip to main content
@juo/blocks/vue is the Vue 3 adapter. It exposes a createVueRenderer for the block renderer slot and a useContext composable that returns service signals as Vue refs.
pnpm add vue
The patterns below extend the Blocks, Reactivity, and Contexts concept pages with Vue-specific equivalents.

Defining a Vue block

// Button/index.ts
import { defineBlock, registerBlock } from "@juo/blocks";
import { createVueRenderer } from "@juo/blocks/vue";
import Button from "./Button.ce.vue";

export const ButtonBlock = defineBlock("Button", {
  group: "theme",
  schema: /* ... */,
  initialValue: () => ({ props: { variant: "primary", label: "Subscribe" } }),
  renderer: createVueRenderer(Button),
});

registerBlock(ButtonBlock);
<!-- Button/Button.ce.vue -->
<script setup lang="ts">
defineProps<{ variant: "primary" | "secondary"; label: string }>();
</script>

<template>
  <button :class="`btn btn--${variant}`">{{ label }}</button>
</template>
createVueRenderer mounts the component inside a <juo-context-root>-aware wrapper. Children rendered by Vue can call useContext and resolve the host’s services.

Reading services with useContext

useContext returns a proxy where every Signal<T> field appears as a Ref<T> — drop it into templates, computed, and watch like any Vue ref.
<script setup lang="ts">
import { useContext } from "@juo/blocks/vue";
import { SubscriptionServiceContext } from "@juo/blocks";
import { computed } from "vue";

const subscription = useContext(SubscriptionServiceContext);

// subscription.current is a Ref<Subscription | null>
const status = computed(() => subscription.current.value?.status ?? "unknown");

async function pause() {
  const id = subscription.current.value?.id;
  if (!id) return;
  await subscription.pause(id);
}
</script>

<template>
  <p>Status: {{ status }}</p>
  <button @click="pause">Pause</button>
</template>
The $ accessor returns the underlying signal when direct signal access is required:
const subscription = useContext(SubscriptionServiceContext);
const raw = subscription.$.current; // Signal<Subscription | null>

Providing contexts from Vue

Use useProvideContext inside a wrapping component:
<script setup lang="ts">
import { useProvideContext } from "@juo/blocks/vue";
import { CartContext, createCart } from "./cart";

useProvideContext(CartContext, createCart());
</script>

Bridging Vue refs and signals

The @juo/blocks/vue entry also exposes toSignal and toRef for the rare cases where crossing the boundary manually is needed:
import { toSignal, toRef } from "@juo/blocks/vue";
import { ref } from "vue";

const myRef = ref(0);
const myRefAsSignal = toSignal(myRef);    // Signal<number>

import { signal } from "@juo/blocks";
const mySignal = signal("hello");
const mySignalAsRef = toRef(mySignal);    // Ref<string>
These utilities are available for cases where crossing the boundary manually is required. The useContext proxy returns refs for most use cases.