Visual Editor
Live-editing in a Nuxt app — enableSync, LocalessDocument, the useLocalessSync composable, and marking elements editable.
Visual Editor
Set enableSync: true in the localess block of nuxt.config.ts to load the Visual Editor sync script (sync-v1.js):
// nuxt.config.ts
localess: {
origin: 'https://my-localess.web.app',
spaceId: 'YOUR_SPACE_ID',
token: process.env.LOCALESS_PUBLIC_TOKEN, // required — sync runs in the browser
enableSync: true,
}Sync needs the public token: the script runs in the browser, so a server-only setup with just serverToken has nothing to sync with. It's a no-op outside the Localess Visual Editor iframe, so it's harmless in a normal browser session — though there's no reason to ship it in a production build that will never be framed by the editor.
LocalessDocument
The whole integration, for most apps. It subscribes to input/change editor events automatically when sync is active and re-renders in place:
<script setup lang="ts">
import { type Content } from '@localess/vue';
import type { Page } from '#shared/models/localess';
const { data: content } = await useAsyncData('content', () => $fetch<Content<Page>>('/api/content'));
</script>
<template>
<LocalessDocument v-if="content" :document="content" />
</template>Nothing else is required — the content was fetched server-side with the secret token, and the live updates arrive over the editor bridge with the public one.
useLocalessSync
For an integration that manages its own state instead of handing it to LocalessDocument, subscribe to the bridge directly. It returns a Ref holding the latest matching event:
<script setup lang="ts">
const latest = useLocalessSync(['input', 'change']);
</script>
<template>
<pre>{{ latest?.data }}</pre>
</template>The subscription registers in onMounted, so it never runs during SSR. The ref stays undefined when sync isn't active — no enableSync, no public token, or not inside the editor iframe.
useLocalessSync is auto-imported.
Marking elements editable
The editor can only select what it can identify. <LocalessComponent> applies the block-level attributes for you; add field-level ones yourself:
<script setup lang="ts">
import { type LocalessSchemaProps } from '@localess/vue';
import type { Page } from '#shared/models/localess';
const props = defineProps<LocalessSchemaProps<Page>>();
</script>
<template>
<main v-bind="localessEditable(props.data)">
<h1 v-bind="localessEditableField<Page>('title')">{{ props.data.title }}</h1>
</main>
</template>localessEditable(data) emits data-ll-id and data-ll-schema; localessEditableField(name) emits data-ll-field. Both are auto-imported and return plain attribute objects, so v-bind is all that's needed. Passing the content type as localessEditableField<Page>('title') restricts the argument to that schema's own field names, so a typo is a compile error.
See Visual Editor for what those attributes mean on the editor side.
Available events
| Event | When |
|---|---|
input | User is typing in a field (real-time preview) |
change | Field value confirmed |
save | Content saved |
publish | Content published |
unpublish | Content unpublished |
pong | Editor heartbeat response |
enterSchema | Editor cursor enters a schema block |
hoverSchema | Editor cursor hovers over a schema block |
leaveSchema | Editor cursor leaves a schema block |