Visual Editor
Live-editing patterns for SvelteKit — LocalessDocument, the localessSync store, and how the sync script gets loaded.
Visual Editor
Set enableSync: true in localessInit() to load the Visual Editor sync script (sync-v1.js). It's a no-op outside the Localess Visual Editor iframe.
With LocalessDocument
<LocalessDocument> wraps <LocalessComponent> and subscribes to input/change events automatically when enableSync is active, re-rendering with the updated content in place. It applies only the edits for its own document (matched by document.id), so a page rendering several documents updates each one independently. It doesn't fetch content — pass the full Content object as document:
<!-- src/routes/[...slug]/+page.svelte — `data.content` comes from +page.server.ts -->
<script lang="ts">
import { LocalessDocument } from '@localess/svelte';
let { data } = $props();
</script>
<LocalessDocument document={data.content} />Props:
| Prop | Type | Required | Description |
|---|---|---|---|
document | Content<T> | Yes | Full content response object (from getContentBySlug/getContentById) |
Renders an inline error message if
document.datais missing. PreferLocalessDocumentoverLocalessComponentfor the top-level content of a page when Visual Editor sync should apply; useLocalessComponentdirectly for nested blocks within an already-synced tree.
With the localessSync store
For custom integrations that manage their own content state instead of using LocalessDocument, subscribe to the Visual Editor bridge directly via the localessSync store — a Readable that emits the latest matching event:
<script lang="ts">
import { localessSync } from '@localess/svelte';
const latest = localessSync(['input', 'change']);
</script>
<p>{$latest?.data}</p>No-ops (never emits) when enableSync was not set in localessInit, or outside the Visual Editor iframe. The store doesn't filter by document: its events carry every document's edits.
With localessSyncOnDocument
To follow one document's edits, localessSyncOnDocument(documentId, callback) calls callback with the edited content for that document only, and returns an unsubscribe function, so return it from $effect to clean up. A subscriber that attaches after the document was already edited is called straight away with the latest edit:
<!-- src/routes/[...slug]/+page.svelte -->
<script lang="ts">
import { localessSyncOnDocument } from '@localess/svelte';
let { data } = $props();
let page = $state(data.content.data);
$effect(() => localessSyncOnDocument(data.content.id, d => (page = d)));
</script>
<h1>{page.title}</h1>It no-ops in the same cases as the store. Unlike @localess/react, there's no exported isSyncEnabled/localessSyncOn helper — localessSync and localessSyncOnDocument are the public entry points into the sync bridge.
Available events:
| Event | When |
|---|---|
input | A field value changed — every keystroke (real-time preview) |
change | Blocks added, removed, duplicated or reordered — and once when the preview connects, with the current content |
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 |
Every input, change, save, publish and unpublish event carries a documentId — the id of the edited document, as returned by the API — so a page rendering several documents can apply each edit to the right one. LocalessDocument (<ll-document> in Angular) filters on it for you; check event.documentId yourself only when you subscribe to raw events. Because edits are matched by documentId, update your Localess deployment together with the SDK — see Troubleshooting.