Quick start #
Install #
pnpm add @dolphinspired/solid-stage
Peer dependency: Solid 1.9+. The library is browser-only (it uses
ResizeObserver and getBoundingClientRect; there is no SSR build).
Vite: exclude Kobalte from dep optimization #
Add this to your vite.config.ts:
optimizeDeps: { exclude: ['@kobalte/core'] },
Without it, the dev server can throw React is not defined when a Kobalte-based
popup first opens, such as the ColorField picker. vite-plugin-solid resolves
Kobalte to raw JSX, but it only sets up your direct Solid dependencies, and Kobalte
reaches you through this library. So Vite's dep optimizer compiles that JSX with its
React default. Production builds aren't affected, so vite build won't warn you.
Clear the cache after adding it, by deleting node_modules/.vite or running Vite once
with --force.
Import the styles once #
The library ships one stylesheet. Import it once, anywhere in your app entry:
import '@dolphinspired/solid-stage/styles.css';
Wrap your app in a .stage-root #
Every token (colour, size, spacing) is declared on .stage-root, and all
portalled UI (select popovers, the modal, tooltips) mounts into the nearest
.stage-root. So your stage must live inside an element carrying that class:
function App() {
return (
<div class="stage-root">
{/* …your stage… */}
</div>
);
}
To restyle, set CSS variables on that element (or any container inside it):
<div class="stage-root" style={{ '--stage-color-accent': '#ff4488' }}>
A minimal stage #
EditorPanels is the outer shell: optional bars, optional left/right panels, and
a required center (your canvas, preview, or any content). A root stage owns the
window (position: fixed; inset: 0).
import { EditorPanels, Section, TextField } from '@dolphinspired/solid-stage';
import { createSignal } from 'solid-js';
import '@dolphinspired/solid-stage/styles.css';
function App() {
const [name, setName] = createSignal('Untitled');
return (
<div class="stage-root">
<EditorPanels
center={<div style={{ display: 'grid', 'place-items': 'center' }}>Your world here</div>}
leftPanel={
<Section title="Settings">
<TextField name="Name" value={name} onChange={setName} />
</Section>
}
/>
</div>
);
}
That's a complete editor: a centre region, a collapsible left panel, and a
collapsible section (whose open/closed state already persists to localStorage)
holding one text field.
Add a reactive store #
Hand-wiring a createSignal per field gets old. createEditorStore gives you
one typed reactive source of truth with optional persistence, and a bind helper
that fills in value + onChange for any field:
import { EditorPanels, FieldGroup, Section, TextField, NumberField, BooleanField, createEditorStore } from '@dolphinspired/solid-stage';
interface Settings {
name: string;
level: number;
snap: boolean;
}
function App() {
const [state, setState, bind] = createEditorStore<Settings>({
defaults: { name: 'Untitled', level: 1, snap: true },
persist: { key: 'my-editor', version: 1 },
});
return (
<div class="stage-root">
<EditorPanels
center={<Viewport settings={state} />}
leftPanel={
<Section title="Settings">
<FieldGroup>
<TextField name="Name" {...bind('name')} />
<NumberField name="Level" {...bind('level')} min={1} />
<BooleanField name="Snap to grid" {...bind('snap')} />
</FieldGroup>
</Section>
}
/>
</div>
);
}
{...bind('name')} expands to value={() => state.name} onChange={(v) => setState('name', v)}.
The store reads/writes through state/setState (Solid's createStore API), so
your canvas reacts with a plain createEffect. See State.
Add a canvas #
For a 3D/2D world in the centre, CanvasStage gives you a sized viewport with
World / HUD / Pointer layers:
import { CanvasStage } from '@dolphinspired/solid-stage';
<CanvasStage onResize={({ width, height }) => renderer.setSize(width, height)}>
<CanvasStage.World mountRef={(el) => el.appendChild(renderer.domElement)} />
<CanvasStage.Hud>{/* overlay UI */}</CanvasStage.Hud>
</CanvasStage>
Drop it into EditorPanels's center. See Layout.
Where next #
- Concepts — the mental model behind all of this.
- Controls & fields — the full widget set.
- Overlays — wire up modal / toast / tooltip (each needs a
one-time host component mounted in your
.stage-root).