Visual Editor
Enable and subscribe to Localess Visual Editor live-editing events from a plain TypeScript app with @localess/live-preview.
Visual Editor Integration
The browser-side Visual Editor helpers live in @localess/live-preview. They are still re-exported from @localess/client, but those re-exports are deprecated.
npm install @localess/live-preview@latestloadLocalessSync(origin, options?)
Injects the Localess Visual Editor sync script into the document <head> and resolves once it has loaded. This enables live-editing capabilities when your site is opened inside the Localess Visual Editor. No-op outside a browser or when not running inside an iframe; concurrent calls share one load.
import { loadLocalessSync } from "@localess/live-preview";
await loadLocalessSync('https://my-localess.web.app');
// With the script's debug mode on
await loadLocalessSync('https://my-localess.web.app', { debug: true });| Option | Type | Default | Description |
|---|---|---|---|
debug | boolean | false | Turns on the sync script's debug mode (data-debug): console logging and on-page messages in the preview |
sdk | string | '@localess/live-preview' | Package name of the SDK loading the script, reported to the Visual Editor as data-sdk with this package's version appended — the editor's connection-status tooltip shows it. Framework SDKs set it to their own name |
syncScriptUrl()
Returns the URL of the Localess sync script, useful for manual script injection.
const scriptUrl = client.syncScriptUrl();Marking Editable Content
Use these helpers to add Localess editable attributes to your HTML elements, enabling element selection and highlighting in the Visual Editor.
localessEditable(content)
Marks a content block as editable.
import { localessEditable } from "@localess/live-preview";
// Returns: { 'data-ll-id': '...', 'data-ll-schema': '...' }
<section {...localessEditable(content.data)}>...</section>localessEditableField<T>(fieldName)
Marks a specific field within a content block as editable, with type-safe field name inference.
import { localessEditableField } from "@localess/live-preview";
// Returns: { 'data-ll-field': 'title' }
<h1 {...localessEditableField<MyPage>('title')}>...</h1>Listening to Visual Editor Events
When your application is loaded inside the Localess Visual Editor, you can subscribe to editing events via window.localess, which the sync script installs once it has loaded.
// `content` is the document this page fetched
if (window.localess) {
// Subscribe to input and change — returns an unsubscribe function
const unsubscribe = window.localess.onChange((event) => {
// An edit to another document rendered on the page
if (event.documentId !== content.id) return;
setPageData(event.data);
});
// Subscribe to one event type, or several
window.localess.on(['save', 'publish'], (event) => {
if (event.documentId === content.id) console.info(`Content ${event.type}d`);
});
}window.localess has these members:
| Member | Description |
|---|---|
version | Generation of the loaded sync script — 'v1' for sync-v1.js |
inEditor | true once the Visual Editor has answered the script's handshake, i.e. the page is actually connected — not just loaded in an iframe. Starts false |
onChange(callback) | Subscribes to input and change. Returns a function that removes the subscription |
on(event, callback) | Subscribes to one event type or an array of them. Returns a function that removes the subscription |
off(event, callback) | Removes a callback added with on or onChange |
Available Event Types
| Event | Payload | Description |
|---|---|---|
input | { type: 'input', documentId: string, data: any } | Fired while a field is being edited (real-time) |
change | { type: 'change', documentId: string, data: any } | Fired when blocks are added, removed, duplicated or reordered — and once when the preview connects, with the current content |
save | { type: 'save', documentId: string } | Fired when content is saved |
publish | { type: 'publish', documentId: string } | Fired when content is published |
unpublish | { type: 'unpublish', documentId: string } | Fired when content is unpublished |
pong | { type: 'pong' } | Heartbeat response from the editor |
enterSchema | { type: 'enterSchema', id, schema, field?, root? } | Fired when entering a schema element |
hoverSchema | { type: 'hoverSchema', id, schema, field? } | Fired when hovering over a schema element |
leaveSchema | { type: 'leaveSchema' } | Fired when leaving a schema element |
documentId is the edited document's Content.id, so a page rendering several documents can apply each edit to the matching one. root: true on enterSchema means the editor went back to the document root, clearing the selection.
Update your Localess deployment together with the SDK: an older deployment sends edits without
documentId, so adocumentIdcheck like the one above drops every edit.