Localess
SvelteKit

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:

PropTypeRequiredDescription
documentContent<T>YesFull content response object (from getContentBySlug/getContentById)

Renders an inline error message if document.data is missing. Prefer LocalessDocument over LocalessComponent for the top-level content of a page when Visual Editor sync should apply; use LocalessComponent directly 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:

EventWhen
inputA field value changed — every keystroke (real-time preview)
changeBlocks added, removed, duplicated or reordered — and once when the preview connects, with the current content
saveContent saved
publishContent published
unpublishContent unpublished
pongEditor heartbeat response
enterSchemaEditor cursor enters a schema block
hoverSchemaEditor cursor hovers over a schema block
leaveSchemaEditor 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.

On this page