Skip to content

Pixi Solid

High-performance 2D scenes, powered by SolidJS reactivity.

Pixi Solid is a library that provides SolidJS components and hooks for working with PixiJS.

  • 📦 Full component coverage — Every major PixiJS display object has a corresponding component.
  • Signals-driven reactivity — State changes automatically update your scene.
  • 🧹 Automatic cleanup — Components clean up after themselves on unmount.
  • 🧪 Testable without a browser — Context, hooks, and ticker are built for simulation.

Terminal window
npm i pixi-solid

Peer dependencies of

{
"pixi.js": "^8.14.3",
"solid-js": "^1.9.10"
}

import { PixiCanvas, Sprite } from "pixi-solid";
import { Texture } from "pixi.js";
export const App = () => (
<PixiCanvas style={{ width: "100%", height: "100vh" }} background="#1099bb">
<Sprite texture={Texture.WHITE} x={100} y={100} />
</PixiCanvas>
);
Declarative scene graphs
Compose PixiJS objects with JSX instead of imperative addChild/removeChild calls. The tree is your scene.
Automatic lifecycle
Components clean up after themselves on unmount: display objects, event listeners, ticker subscriptions, textures. No manual disposal tracking.
Shared reactivity
Signals and stores drive both canvas content and HTML UI from the same state. No bridging layer or two-way sync required.
Unified timing
Animations, frame callbacks, and sprite updates all synchronise to the same ticker context. No timing drift between onTick, useSpring, or AnimatedSprite.
Full PixiJS coverage
Props, refs, event handlers, and compatibility with plain PixiJS children if you need it.
Testable by design
Context providers, hooks, and the ticker are structured for simulation in tests. No browser or canvas required for unit tests.

import { CRTFilter } from "pixi-filters";
import { Container, onTick, PixiCanvas, Sprite, usePixiScreen, TilingSprite } from "pixi-solid";
import { objectFit, ObjectFitContainer } from "pixi-solid/utils";
import type * as Pixi from "pixi.js";
import { Assets, BlurFilter, Rectangle } from "pixi.js";
import { createResource, onCleanup, Show } from "solid-js";
import { Character } from "./character";
import { Controls } from "./controls";
import { createPlayerStore } from "./player-store";
import { loadSceneAssets } from "./load-scene-assets";
const DemoScene = (props: { isRunning: boolean; direction: "left" | "right" }) => {
const pixiScreen = usePixiScreen();
const sceneBounds = new Rectangle(0, 0, 400, 400);
const blurFilter = new BlurFilter({ strength: 8 });
const crtFilter = new CRTFilter({
curvature: 3,
lineContrast: 0.1,
vignetting: 0.1,
noise: 0.04,
noiseSize: 2,
});
onTick(({ deltaTime }) => {
crtFilter.seed = Math.random() * 10;
crtFilter.time += deltaTime * 0.3;
});
onCleanup(() => {
blurFilter.destroy();
crtFilter.destroy();
});
return (
<ObjectFitContainer width={pixiScreen.width} height={pixiScreen.height} fitMode="cover">
<Container filters={crtFilter} boundsArea={sceneBounds}>
<Sprite
label="Sky"
texture={Assets.get<Pixi.Texture>("sky")}
filters={blurFilter}
ref={(ref) => {
objectFit(ref, sceneBounds, "cover");
}}
/>
<TilingSprite
label="Ground"
width={sceneBounds.width}
height={sceneBounds.height * 0.3}
position={{ x: 0, y: sceneBounds.height * 0.7 }}
ref={(tileRef) => {
onTick(({ deltaTime }) => {
const movementSpeed = props.isRunning ? 1.3 : 0;
const directionMultiplier = props.direction === "left" ? 1 : -1;
tileRef.tilePosition.x += movementSpeed * directionMultiplier * deltaTime;
});
}}
texture={Assets.get<Pixi.Texture>("ground")}
/>
<Character
direction={props.direction}
isRunning={props.isRunning}
position={{ x: sceneBounds.width * 0.5, y: sceneBounds.height * 0.7 }}
/>
</Container>
</ObjectFitContainer>
);
};
export const DemoApp = () => {
const playerStore = createPlayerStore();
const [textureResource] = createResource(loadSceneAssets);
return (
<div style={{ position: "relative" }}>
<Controls
isRunning={playerStore.state.isRunning}
direction={playerStore.state.direction}
onToggleDirectionClicked={playerStore.toggleDirection}
onToggleRunningClicked={playerStore.toggleRunning}
/>
<PixiCanvas
style={{
"aspect-ratio": "16/9",
overflow: "hidden",
"border-radius": "10px",
}}
>
<Show when={textureResource()}>
<DemoScene
isRunning={playerStore.state.isRunning}
direction={playerStore.state.direction}
/>
</Show>
</PixiCanvas>
</div>
);
};