Svelte Integration
Managed UI Svelte 5 components run inside the same generated UI DI container that React and Vue components use. The framework adapter is selected via a single project-wide flag in hexa-cli.config.json:
{
"ui": {
"framework": "svelte",
"popup": { "mode": "managed", "sourceDir": "ui/popup", "indexFile": "index.html" }
}
}
When ui.framework: "svelte" is set, HexaJS injects @sveltejs/vite-plugin-svelte (configured with emitCss: true) into the popup/devtools/newtab build pipelines and emits content @View overlays that mount Svelte components inside shadow DOM via SvelteShadowRenderer from @hexajs-dev/ui/svelte.
Svelte support requires
svelte@^5and@sveltejs/vite-plugin-svelte@^6. The framework choice is project-wide; mixing Svelte with React or Vue per surface is not supported.
Request data in onMount
<script lang="ts">
import { onMount } from 'svelte';
import { inject } from '@hexajs-dev/common';
import { HexaUIClient } from '@hexajs-dev/ui';
import { configApi } from './api';
import type { ConfigResponseMessage, GetConfigMessage } from './messages';
let config = $state<unknown | null>(null);
onMount(async () => {
const hexaUIClient = inject(HexaUIClient);
const response = await hexaUIClient.sendMessage<GetConfigMessage, ConfigResponseMessage>(
configApi.Get,
new GetConfigMessage(Date.now())
);
if (response && !hasHexaError(response) && response.config) {
config = response.config;
}
});
</script>
{#if config}
<pre>{JSON.stringify(config, null, 2)}</pre>
{/if}
inject(...) here is the HexaJS DI helper from @hexajs-dev/common. The $state rune is the Svelte 5 reactive primitive — no wrapping in ref or reactive is needed.
Send changes from an event handler
<script lang="ts">
import { inject } from '@hexajs-dev/common';
import { HexaUIClient } from '@hexajs-dev/ui';
import { configApi } from './api';
import type { ConfigResponseMessage, UpdateConfigMessage } from './messages';
async function setTheme(nextTheme: 'light' | 'dark'): Promise<void> {
const hexaUIClient = inject(HexaUIClient);
await hexaUIClient.sendMessage<UpdateConfigMessage, ConfigResponseMessage>(
configApi.Update,
new UpdateConfigMessage({ theme: nextTheme })
);
}
</script>
<button onclick={() => setTheme('dark')}>Dark mode</button>
Resolve token values
<script lang="ts">
import { HEXA_PLATFORM, inject } from '@hexajs-dev/common';
const platform = inject(HEXA_PLATFORM);
</script>
<span>{platform}</span>
Shadow @View overlays
Svelte components mounted inside a shadow DOM via @View receive the controller instance through the controller prop, exactly matching the React and Vue renderer contracts.
Component-scoped CSS is automatically isolated in the shadow root because the plugin is configured with emitCss: true. Import the stylesheet with ?inline and pass it as styles to the @View decorator:
// sample-overlay.view.ts
import { HexaView, View } from '@hexajs-dev/core';
import SampleOverlayComponent from './sample-overlay.component.svelte';
import styles from './sample-overlay.css?inline';
@View({ id: 'sample', component: SampleOverlayComponent, styles, anchorSelector: 'body' })
export class SampleOverlayView extends HexaView {
count = 0;
increment = (): number => ++this.count;
}
<!-- sample-overlay.component.svelte -->
<script lang="ts">
import type { SampleOverlayView } from './sample-overlay.view';
const { controller }: { controller: SampleOverlayView } = $props();
let count = $state(controller.count);
function tick(): void {
count = controller.increment();
}
</script>
<button type="button" onclick={tick}>Count: {count}</button>
The component receives controller: SampleOverlayView via $props() — the Svelte 5 rune that replaces export let. This keeps the @InjectView integration uniform across React, Vue, and Svelte.
Important scope reminder
Managed UI Svelte components resolve UI/general services and tokens. They do not resolve HexaBackgroundStore/HexaContentStore directly; ask the background through HexaUIClient messages and let state live in the appropriate context.
Classes
HexaUIClient
UI-context HexaClient. Sends messages from popup/devtools UI to the background.
import { HexaUIClient } from '@hexajs-dev/ui';
class HexaUIClient { ... }
Methods
sendMessage()
Send a message and await a response. Content → background uses runtime.sendMessage. Background → content requires a tabId — use BackgroundHexaClient.sendToTab().
sendMessage<TPayload, TResponse>(target: `${namespace}:${api}`, payload?: TPayload): Promise<TResponse>