<juo-context-root>, provides services, registers blocks, and decides what to render where. Themes ship one. So can extensions and standalone storefront surfaces. This guide walks through building a portal from scratch, then layering on the editor integration.
Prefer to learn from a working example? The sample-beauty-portal repository is a complete reference portal — registered blocks, services wired up, editor mode enabled — to clone and adapt.
Install
@juo/blocks ships as a single package:
vue, react, and preact are optional peer dependencies — install only the one the portal runs on, or none for plain DOM rendering.
Entry points
@juo/blocks exposes a handful of subpath entry points. Import from the ones that match the stack — bundlers tree-shake the rest:
| Entry point | Purpose |
|---|---|
@juo/blocks | Core: block system, signals, contexts, services |
@juo/blocks/vue | Vue 3 renderers and composables |
@juo/blocks/react | React renderers and hooks |
@juo/blocks/preact | Preact renderers and hooks |
@juo/blocks/editor | Bridge for integrating with the Juo Editor |
@juo/blocks/web-components/editor | Custom elements with Editor affordances (selection, inline text editing) |
@juo/blocks/web-components/runtime | Lean custom elements for storefront rendering |
Anatomy
A portal does four things:- Loads the web components. Pick the runtime or editor build.
- Mounts a
<juo-context-root>in the HTML shell. - Provides services on the root.
- Registers blocks so the editor and runtime can instantiate them by name.
setupEditorMode to advertise its blocks and routes. See Editor for what that protocol carries.
Runtime: the bare minimum
<juo-page route="/"> element subscribes to ThemeStateContext, resolves the / route, and renders the resulting root block. Nested blocks resolve services from the surrounding context tree.
Setup: providing services and rendering the page
The app shell registers blocks and callsprovideContext for each service on the <juo-context-root>. <juo-page> then injects ThemeStateContext to resolve the current route and render the resulting root block.
Composition: rendering a block from theme state
The resolved root block emits one or more<juo-extension-root> slots; the children placed in each slot are themselves blocks. Each block calls injectContext to pull services out of the surrounding context tree, and the renderer turns its props plus those services into DOM.
Adding editor support
When the merchant opens the portal inside the editor, the iframe loads the editor build of the web components and the portal needs to advertise its blocks and routes. Detect the editor (e.g. by checkingwindow.parent !== window or a URL flag), load the right web component bundle, and call setupEditorMode:
setupEditorMode:
- Serializes every registered block (name, group, schema, initial value, presets, locales) and sends it to the editor.
- Reports the portal’s routes from
routerService.getPages(). - Subscribes to incoming editor messages (selection, prop updates, theme state changes, translation overrides) and applies them to the local
ThemeState.
Preset previews with useBridge
The editor’s block picker shows presets for each block (see Blocks → Presets). When the merchant hovers a preset, the editor sends a SET_BLOCK_PRESET message and the matching block can render a preview without committing.
A block opts into preset previews using useBridge from its framework adapter:
useBridge is a no-op outside the editor — isEditorMode returns false and onPresetChange never fires. You can ship the same block to both modes without branching.
Folder layout
A typical portal package looks like:defineBlock calls in their own folder per block — each block is self-contained, and registerBlock is the only thing that needs to touch them all.