Rendering
Every extension entry point must call Lumio.render() to mount the React component tree. This is the only way to display content in an extension surface.
Syntax
import { Lumio } from "@zaflun/lumio-sdk";
Lumio.render(<MyComponent />, { target: "editor" | "layer" | "interactive" });
Targets
| Target | Surface | Entry file |
|---|---|---|
"editor" | Dashboard sidebar | src/editor.tsx |
"layer" | OBS Browser Source | src/layer.tsx |
"interactive" | Standalone page | src/interactive.tsx |
The target value must match the file's surface. Passing the wrong target causes a runtime error.
10-second timeout
Lumio.render() must be called within 10 seconds of page load. If the timeout expires, the extension is terminated and the user sees an error message with a "Reload" option.
// This will cause termination — render is called too late
setTimeout(() => {
Lumio.render(<App />, { target: "editor" });
}, 15000); // 15 seconds — too late!
The correct pattern is to render immediately with a loading state:
// Correct: render immediately, show loading state internally
function App() {
const { data, isLoading } = useQuery("getData");
if (isLoading) {
return (
<CompactView title="My Extension">
<Text content="Loading..." variant="muted" />
</CompactView>
);
}
return <MainUI data={data} />;
}
Lumio.render(<App />, { target: "editor" }); // called immediately
Single call requirement
Lumio.render() must be called exactly once per entry file. Calling it multiple times is an error:
// Wrong: calling render twice
Lumio.render(<App />, { target: "editor" });
Lumio.render(<OtherApp />, { target: "editor" }); // Error!
To conditionally render different content, use React's conditional rendering inside a single component:
// Correct: one render call, conditional inside
function Root() {
const [storage] = useExtensionStorage();
return storage.mode === "advanced" ? <AdvancedUI /> : <SimpleUI />;
}
Lumio.render(<Root />, { target: "editor" });
No direct DOM access
Extensions must use only SDK components. The React reconciler serializes the component tree to a JSON intermediate representation (VisionNode tree) that the Lumio host renders as actual DOM elements in the sandboxed iframe.
// These have no effect inside the extension sandbox:
document.createElement("div"); // no-op
document.getElementById("root"); // returns null
document.body.appendChild(element); // no-op
Use SDK layout and display components instead:
// Use SDK components for all rendering
import { Box, Text, VStack } from "@zaflun/lumio-sdk";
function Layout() {
return (
<VStack>
<Box style={{ padding: 16 }}>
<Text content="Hello World" variant="heading" />
</Box>
</VStack>
);
}
VisionStyle
All components accept a style prop of type VisionStyle — a typed subset of CSS properties that the host sanitizes before rendering.
Allowed properties:
| Category | Properties |
|---|---|
| Box model | margin, padding, width, height, minWidth, maxWidth, minHeight, maxHeight |
| Flexbox | display, flexDirection, flexWrap, flex, alignItems, alignSelf, justifyContent, gap, flexShrink, flexGrow |
| Position | position: "relative" | "absolute", top, right, bottom, left, zIndex |
| Visual | background, backgroundColor, color, opacity, border, borderRadius, boxShadow |
| Typography | fontSize, fontWeight, lineHeight, letterSpacing, textAlign, textTransform, textDecoration |
| Transform | transform, transition, animation, objectFit |
| Misc | cursor, pointerEvents, overflow, overflowX, overflowY |
Blocked properties (rejected by the host sanitizer):
position: "fixed"orposition: "sticky"- CSS
expression()values javascript:URLscontentwith external resources
Provider wrapping
Lumio.render() automatically wraps the component tree with all necessary context providers:
- Storage context (
useExtensionStorage) - Config context (
useLumioConfig) - Theme context (
useLumioTheme) - Identity context (
useLumioIdentity) - Event subscription context (
useLumioEvent)
You do not need to add any providers manually.