Svelte 5 integration for Localess. Rendering-only: component registry, editable attributes, Visual Editor sync, rich-text rendering. Does not fetch data — see "SSR with SvelteKit" for how server-side fetching fits in. There is no Vite plugin — register components by passing a components map to localessInit() directly (see "Component Registry" below for why).
Peer dependency: Svelte ^5.0.0.
npm install @localess/svelte sveltelocalessInit() must run synchronously during a component's initialization (Svelte's setContext constraint) — call it at the top of a root +layout.svelte's <script>, not inside onMount:
<!-- +layout.svelte -->
<script lang="ts">
import { localessInit } from '@localess/svelte';
import type { Snippet } from 'svelte';
let { children }: { children: Snippet } = $props();
localessInit({
origin: import.meta.env.VITE_LOCALESS_ORIGIN,
spaceId: import.meta.env.VITE_LOCALESS_SPACE_ID,
token: import.meta.env.VITE_LOCALESS_TOKEN, // public token only
components: { page: Page, button: Button },
enableSync: true,
});
</script>
{@render children()}<script lang="ts">
import { getLocaless, LocalessComponent } from '@localess/svelte';
const content = await getLocaless().getContentBySlug('home');
</script>
<LocalessComponent data={content.data} assets={content.assets} links={content.links} references={content.references} />Wraps LocalessComponent and subscribes to Visual Editor input/change events automatically when enableSync is active, updating the rendered content in place. Does not fetch content — pass the full Content object (e.g. from getLocaless().getContentBySlug(...) or a SvelteKit load() function) as the document prop.
<script lang="ts">
import { getLocaless, LocalessDocument } from '@localess/svelte';
const content = await getLocaless().getContentBySlug('home');
</script>
<LocalessDocument document={content} />Prefer LocalessDocument over LocalessComponent whenever the rendered content should update live inside the Visual Editor iframe; use LocalessComponent directly for nested blocks within an already-synced tree.
Pass components: { schemaKey: Component } to localessInit({...}) directly:
<script lang="ts">
import { localessInit } from '@localess/svelte';
import Page from './lib/components/localess/Page.svelte';
import Button from './lib/components/localess/Button.svelte';
localessInit({ origin, spaceId, token, components: { page: Page, button: Button } });
</script>There's no Vite plugin auto-discovering these from a folder. That was tried and removed: localessInit()'s setContext call only works when it runs synchronously during a component's own initialization, and a Vite virtual module's top-level code always finishes evaluating before the importing component's function body runs — so a plugin-generated module can never safely drive it.
<LocalessComponent data={blok} /> <!-- applies data-ll-id/data-ll-schema automatically -->
<section use:localessEditable={blok}>...</section> <!-- manual application -->
<h1 {...localessEditableField<Page>('title')}>{blok.title}</h1> <!-- field-level, spread onto the element -->enableSync: true (in localessInit({...})) injects the sync script and activates the localessSync store. It's a no-op outside the Localess Visual Editor iframe.
<script lang="ts">
import { localessSync } from '@localess/svelte';
const latest = localessSync(['input', 'change']);
</script>
<p>{$latest?.data}</p>Built on @localess/richtext (see docs/richtext.md) — no TipTap at runtime, reactive via $derived (updates on Visual Editor live-sync):
<script lang="ts">
import { LocalessRichText, type LocalessSchemaProps } from '@localess/svelte';
import type { Article } from '../shared/models/localess'; // your content types — `body` is a `ContentRichText` field
let { data }: LocalessSchemaProps<Article> = $props();
</script>
<LocalessRichText content={data.body} />The content prop is a LocalessRichTextInput (a ContentRichText field value, a rich text document/node/node array, or null/undefined); output is an HTML string rendered via {@html}. String-based per-node overrides: <LocalessRichText content={data.body} renderers={{ paragraph: ({ children }) =>
}} />. A custom link renderer receives an already-sanitized attrs.href, but a string renderer must still escape what it interpolates: use escapeAttr for attribute values and escapeHtml for text (both re-exported, with sanitizeUrl).
@localess/svelte is rendering-only — SSR data-fetching goes through SvelteKit's own +page.server.ts load() convention, calling localessClient (re-exported from @localess/svelte — never import @localess/client directly) with a secret token:
// src/routes/[...slug]/+page.server.ts
import { localessClient } from '@localess/svelte';
import { LOCALESS_ORIGIN, LOCALESS_SPACE_ID, LOCALESS_TOKEN } from '$env/static/private';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
const client = localessClient({
origin: LOCALESS_ORIGIN,
spaceId: LOCALESS_SPACE_ID,
token: LOCALESS_TOKEN, // secret, server-only
});
return { content: await client.getContentBySlug(params.slug || 'home') };
};<!-- src/routes/[...slug]/+page.svelte -->
<script lang="ts">
import { LocalessDocument } from '@localess/svelte';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>
<LocalessDocument document={data.content} />SvelteKit's own data-prop serialization hydrates the server-fetched result to the client — @localess/svelte needs no hydration mechanism of its own. Call localessInit() in the root +layout.svelte with a public token only if you also want Visual Editor sync on top, then LocalessDocument picks up live input/change events automatically; use LocalessComponent instead if you don't need sync.
| Export | Kind | Description |
|---|---|---|
localessInit(options) |
Function | Initializes the client + component registry, sets Svelte context |
getLocaless() |
Function | Returns the client from context |
localessClient(options) |
Function | Raw client factory, re-exported from @localess/client, for standalone SSR data-loading outside localessInit |
LocalessComponent |
Component | Dynamic schema-to-component renderer |
LocalessDocument |
Component | Wraps LocalessComponent and re-renders on Visual Editor sync events |
localessEditable |
Action | Applies data-ll-id/data-ll-schema |
localessEditableField(name) |
Function | Applies data-ll-field, spread onto an element |
localessSync(event) |
Function | Visual Editor bridge event subscription, returns a Readable |
LocalessRichText |
Component | Renders a rich text field (content, renderers? props), reactive via $derived |
escapeHtml, escapeAttr, sanitizeUrl |
Functions | Escaping and link-href allowlist for custom string renderers, re-exported from @localess/richtext |
LocalessApiError |
Class | Re-exported from @localess/client |
LocalessComponentProps, LocalessDocumentProps, LocalessSchemaProps, LocalessSvelteInitOptions |
Types | Local prop/option types |
LocalessClient, LocalessClientOptions, EventToAppOf, EventToAppType |
Types | Re-exported from @localess/client |
Content, ContentData, ContentDataSchema, Assets, Links, References |
Types | Re-exported from @localess/model |
LocalessRichTextDocument, LocalessRichTextInput, LocalessRichTextMark, LocalessRichTextNode |
Types | Re-exported from @localess/richtext |
See packages/svelte/SKILL.md for the full usage guide (also shipped inside the npm package).