Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sanchedev/fraxel/llms.txt

Use this file to discover all available pages before exploring further.

Every game object in Fraxel is built from nodes — JSX elements that map directly to engine classes. Nodes are the building blocks of the scene tree: each one owns its own lifecycle, children, events, and (for 2D nodes) a position in world space. You compose nodes by nesting JSX, and the engine manages their start, update, draw, and destroy phases automatically.

Node Overview

NodeJSX TagDescription
Transform<transform>Positioning container for child nodes
Group<group>Logical container (no spatial positioning)
Sprite<sprite>Displays a texture with optional filters
AnimationPlayer<animation-player>Plays frame-based animations
Collider<collider>Detects overlaps with other colliders
RayCast<ray-cast>Projects a ray to detect colliders
Clickable<clickable>Detects click/hover pointer events
Rectangle<rectangle>Renders a filled/stroked rectangle
Timer<timer>Counts up and fires events
Text<text>Renders text on the canvas
AudioPlayer<audio-player>Plays audio buffers
Camera<camera>Controls the viewport
RigidBody<rigid-body>Adds physics simulation
To get a typed reference to a node at runtime, pass a PrimaryNode enum value to useNode(). For example: useNode(PrimaryNode.Sprite), useNode(PrimaryNode.Collider), useNode(PrimaryNode.Timer). Every built-in node type has a corresponding PrimaryNode variant: Group, Transform, Sprite, AnimationPlayer, Collider, RayCast, Clickable, Timer, Rectangle, Text, AudioPlayer, Camera, and RigidBody.

Common Node Props

Every node, regardless of type, accepts these shared props inherited from the base Node class:
PropTypeDefaultDescription
refNodeReference<T>Attach a useNode() reference to access the node imperatively
idstring | symbolauto symbolIdentifier for useChild() path lookup. Must match [a-zA-Z][a-zA-Z0-9-_]*
zIndexnumber0Draw order relative to siblings (higher = drawn later / on top)
deltaIncreasenumber1Multiplies delta for this node and all its children (speed scaling)
scriptFraxelScript<T>Attach a FraxelScript instance to this node
childrenNode[]Child nodes (normally written as JSX children)

Nodes

A Transform is a spatial container that positions its children in 2D space. It has no visual output itself — it simply applies a coordinate offset to everything inside it. Use <transform> to group related nodes or to give a logical game object a single point of control.
import { useNode } from 'fraxel/hooks'
import { PrimaryNode, Vector2 } from 'fraxel'

function Player() {
  const body = useNode(PrimaryNode.Transform)

  return (
    <transform ref={body} position={new Vector2(100, 200)}>
      <sprite textureId={PLAYER_TEXTURE} />
      <collider shape={shapes.circle(16)} group={['player']} collidesWith={['enemy']} />
    </transform>
  )
}
Props
PropTypeDefaultDescription
positionVectorLike | SignalGetter[0, 0]Position in world space. Accepts [x, y], Vector2, or a signal getter
Inherits all common node props.
A Group is a logical container without spatial positioning. Unlike <transform>, it does not apply any coordinate offset to its children — it simply groups them for organizational or lifecycle purposes.
<group>
  <sprite textureId={playerTexture} />
  <sprite textureId={shadowTexture} />
</group>
<group> is used internally by the <List> component as an anchor for keyed reconciliation.PropsInherits all common node props. No additional props.
The Sprite node draws a texture on the canvas. It supports texture atlas slicing via margin and sourceSize, display scaling via displaySize, axis flipping, and a rich set of CSS-compatible visual filters. All filter props are reactive.
import { loadTexture } from 'fraxel/assets'
import { useNode } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

const PLAYER = await loadTexture('/assets/player.png')

function Player() {
  const sprite = useNode(PrimaryNode.Sprite)
  const [health] = useSignal(100)

  return (
    <sprite
      ref={sprite}
      textureId={PLAYER}
      sourceSize={[16, 16]}
      displaySize={[32, 32]}
      brightness={1.2}
      modulate={[1, 0.5, 0, 1]}
      grayscale={() => health() <= 0 ? 1 : 0}
    />
  )
}
Props
PropTypeDefaultDescription
positionVectorLike | SignalGetter[0, 0]Position in world/parent space
textureIdsymbol | SignalGetter<symbol>Symbol returned by loadTexture()
marginVectorLike | SignalGetter[0, 0]Texture offset (for sprite sheet frames)
sourceSizeVectorLike | SignalGetterFull textureRegion of the texture to render
displaySizeVectorLike | SignalGettersourceSizeRendered size of the sprite on canvas
flipXboolean | SignalGetter<boolean>falseMirror horizontally
flipYboolean | SignalGetter<boolean>falseMirror vertically
brightnessnumber | SignalGetter<number>10 = black, 1 = base, 2 = white
grayscalenumber | SignalGetter<number>00 = full color, 1 = fully grayscale
modulateColor | SignalGetter<Color>[1,1,1,1]RGBA tint multiplier. Each channel 01
contrastnumber | SignalGetter<number>10 = flat, 1 = base, 2 = double contrast
saturatenumber | SignalGetter<number>10 = desaturated, 1 = base, 2 = double saturation
hueRotatenumber | SignalGetter<number>0Hue rotation in degrees (0360)
invertnumber | SignalGetter<number>00 = normal, 1 = fully inverted
opacitynumber | SignalGetter<number>10 = transparent, 1 = opaque
The Clickable node detects pointer interactions within a defined hit area. Place it as a child of a <sprite> or <transform> to make that area interactive.
import { useNode } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function Button() {
  const sprite = useNode(PrimaryNode.Sprite)

  return (
    <sprite ref={sprite} textureId={BTN_TEXTURE}>
      <clickable
        size={[64, 32]}
        onClick={(pos) => console.log('clicked at', pos)}
        onMouseEnter={() => console.log('hover in')}
        onMouseExit={() => console.log('hover out')}
      />
    </sprite>
  )
}
Props
PropTypeDefaultDescription
sizeVectorLikeRequired. Width and height of the clickable hit area
disabledbooleanfalseDisables all pointer interactions when true
onClick(pos: Vector2) => voidFires once when the pointer clicks inside the area; receives local position
onMouseEnter() => voidFires once when the pointer enters the area
onMouseExit() => voidFires once when the pointer leaves the area
Events (for useEvent)
Event NameCallback SignatureDescription
clicked(pos: Vector2) => voidPointer clicked inside the area; receives local position
mouseEntered() => voidPointer entered the area
mouseExited() => voidPointer left the area
mouseOver(pos: Vector2) => voidFires every frame while the pointer is inside; pos is local
Set gameConfig.testOptions.showClickables = true to draw hit-area outlines during development.
The Timer node counts elapsed time and fires events at each tick and when it reaches its duration. It has no visual output but can hold child nodes.
import { useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function Cooldown() {
  const timer = useNode(PrimaryNode.Timer)

  useEvent(timer, 'timeout', () => {
    console.log('Cooldown finished!')
  })

  useEvent(timer, 'timeChanged', (elapsed) => {
    console.log('Elapsed:', elapsed) // seconds
  })

  return <timer ref={timer} duration={3} autoPlay />
}
Props
PropTypeDefaultDescription
durationnumber | SignalGetter<number>Required. Total duration in seconds
autoPlaybooleanfalseIf true, timer starts as soon as the node mounts
Events (for useEvent)
Event NameCallback SignatureDescription
timeout() => voidFires when the timer reaches duration
timeChanged(elapsed: number) => voidFires every frame while playing; receives elapsed seconds
Methods (on timer.node)
MethodSignatureDescription
play(from?)(from?: number) => voidStart or resume. Optionally start from a time in seconds
pause()() => voidPause without resetting elapsed time
stop()() => voidStop and reset elapsed time to 0
The Collider node detects overlapping collisions with other Collider nodes. It supports rectangle and circle shapes and uses a group-based filter to control which colliders interact with which.
import { useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode, shapes } from 'fraxel'

function Enemy() {
  const collider = useNode(PrimaryNode.Collider)

  useEvent(collider, 'colliderEntered', (other) => {
    console.log('Hit by:', other)
  })

  return (
    <collider
      ref={collider}
      shape={shapes.circle(8)}
      group={['enemy']}
      collidesWith={['projectile']}
    />
  )
}
Props
PropTypeDefaultDescription
shapeShapeRequired. shapes.rectangle(w, h) or shapes.circle(r)
groupstring[]Required. Groups this collider belongs to
collidesWithstring[]Required. Groups this collider reacts to
Events (for useEvent)
Event NameCallback SignatureDescription
colliderEntered(other: Collider) => voidFires once when overlap begins
collided(other: Collider) => voidFires every frame while overlapping
colliderExited(other: Collider) => voidFires once when overlap ends
Set gameConfig.testOptions.showColliders = true to visualize collider shapes during development.
The RayCast node projects a ray from its position in a given direction and fires events when that ray intersects a Collider belonging to one of the collidesWith groups.
import { useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode, Vector2 } from 'fraxel'

function Detector() {
  const ray = useNode(PrimaryNode.RayCast)

  useEvent(ray, 'colliderEntered', (collider) => {
    console.log('Detected:', collider)
  })

  useEvent(ray, 'colliderExited', (collider) => {
    console.log('Lost:', collider)
  })

  return (
    <ray-cast
      ref={ray}
      direction={new Vector2(100, 0)}
      collidesWith={['enemy']}
    />
  )
}
Props
PropTypeDefaultDescription
directionVectorLikeRay direction vector (also encodes length)
collidesWithstring[]Required. Groups the ray detects
Events (for useEvent)
Event NameCallback SignatureDescription
colliderEntered(collider: Collider) => voidFires when the ray first hits a collider
colliderExited(collider: Collider) => voidFires when the ray stops hitting a collider
The Rectangle node draws a filled and optionally stroked rectangle directly on the canvas. All visual props are reactive.
import { useNode, useSignal, useComputed } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function HealthBar() {
  const bar = useNode(PrimaryNode.Rectangle)
  const [health] = useSignal(100)
  const fill = useComputed(() => [1 - health() / 100, health() / 100, 0, 1] as Color)

  return (
    <rectangle
      ref={bar}
      position={[10, 10]}
      size={[128, 16]}
      fillColor={fill}
      strokeColor={[0, 0, 0, 1]}
      strokeWidth={2}
    />
  )
}
Props
PropTypeDefaultDescription
positionVectorLike | SignalGetter[0, 0]Position in parent space
sizeVectorLike | SignalGetterRequired. Width and height of the rectangle
fillColorColor | SignalGetter<Color>[1,1,1,1]RGBA fill color. Each channel 01
strokeColorColor | SignalGetter<Color>RGBA border color. If omitted, no border is drawn
strokeWidthnumber | SignalGetter<number>1Border width in pixels
The AnimationPlayer node drives frame-based sprite animations. It targets a <sprite> sibling and updates its margin, sourceSize, and displaySize each frame to step through keyframes.
import { useNode } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function Character() {
  const anim = useNode(PrimaryNode.AnimationPlayer)
  const [state] = useSignal<'idle' | 'walk'>('idle')

  return (
    <transform>
      <sprite textureId={SHEET} />
      <animation-player
        ref={anim}
        animations={() => ({
          idle: idleFrames,
          walk: walkFrames,
        })}
        currentAnim={() => state()}
      />
    </transform>
  )
}
Props
PropTypeDefaultDescription
animations() => Record<string, Frame[]>Required. Function returning the animation map (deferred call)
currentAnimstring | SignalGetter<string>Name of the animation to play. Reactive for auto-switching
destroyOnEndbooleanfalseDestroy the node after the animation finishes
animations is called as a deferred function when the node starts, not during construction. This allows you to safely reference sibling sprite nodes that may not be available at JSX evaluation time.
The Text node renders a string on the canvas using ctx.fillText(). Its text prop is reactive, so it re-renders automatically when a signal changes.
import { useNode, useSignal } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function ScoreLabel() {
  const label = useNode(PrimaryNode.Text)
  const [score] = useSignal(0)

  return (
    <text
      ref={label}
      position={[10, 20]}
      text={() => `Score: ${score()}`}
      style={{
        fontSize: 16,
        foregroundColor: '#ffffff',
        fontFamily: 'monospace',
      }}
    />
  )
}
Props
PropTypeDefaultDescription
positionVectorLike | SignalGetter[0, 0]Position in parent space
textstring | SignalGetter<string>Required. The string to render. Reactive via SignalGetter
stylePartial<TextStyle>TextStyle.DEFAULTPartial style object. See TextStyle for all fields
TextStyle fields
FieldTypeDescription
fontSizenumberFont size in pixels
fontFamilystringCSS font family string
fontWeightstringCSS font weight ('normal', 'bold', etc.)
foregroundColorstringCSS color string for fill
textAlignCanvasTextAlign'left', 'center', 'right', etc.
The AudioPlayer node plays audio buffers loaded with loadSound(). It supports looping, volume, playback rate, and deferred destruction until a sound finishes.
import { useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'
import { loadSound } from 'fraxel/assets'

const SHOOT = await loadSound('/assets/shoot.mp3')

function Gun() {
  const audio = useNode(PrimaryNode.AudioPlayer)
  const clickable = useNode(PrimaryNode.Clickable)

  useEvent(clickable, 'clicked', () => {
    audio.node.play()
  })

  return (
    <sprite ref={clickable} textureId={GUN}>
      <clickable size={[32, 32]} />
      <audio-player ref={audio} soundId={SHOOT} volume={0.8} />
    </sprite>
  )
}
Props
PropTypeDefaultDescription
soundIdsymbolRequired. Symbol returned by loadSound()
loopbooleanfalseLoop the audio
volumenumber1Playback volume (01)
playbackRatenumber1Playback speed multiplier
persistUntilEndbooleanfalseDefer node destruction until the current sound finishes playing
Events (for useEvent)
Event NameCallback SignatureDescription
ended() => voidFires when playback finishes
error() => voidFires on a playback error
Methods (on audio.node)
MethodSignatureDescription
play(offset?)(offset?: number) => voidStart playback, optionally from offset
pause()() => voidPause without resetting position
stop()() => voidStop and reset to beginning
The Camera node controls the viewport — which part of the world is visible on screen. Its follow() method makes the camera track a target node automatically each frame.
import { useNode, useMount } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function GameScene() {
  const camera = useNode(PrimaryNode.Camera)
  const player = useNode(PrimaryNode.Sprite)

  useMount(() => {
    camera.node.follow(player.node)
  })

  return (
    <camera ref={camera} zoom={2}>
      <sprite ref={player} textureId={PLAYER} />
      <sprite textureId={BG} />
    </camera>
  )
}
Props
PropTypeDefaultDescription
positionVectorLike | SignalGetter[0,0]Camera position in world space
zoomnumber1Viewport zoom level
Methods (on camera.node)
MethodSignatureDescription
follow(target)(target: Node2D | undefined) => voidTrack a node every frame, or pass undefined to stop following
The RigidBody node adds physics simulation to a game object. It must be placed as a sibling of a <collider> node. The physics engine uses the collider’s shape for all calculations.
import { shapes } from 'fraxel'

function FallingRock() {
  return (
    <transform position={[100, 0]}>
      <sprite textureId={ROCK} />
      <collider shape={shapes.circle(16)} group={['rock']} collidesWith={['ground']} />
      <rigid-body mass={2} bounce={0.6} />
    </transform>
  )
}
Props
PropTypeDefaultDescription
massnumber1Mass of the physics body
frictionnumber0.5Surface friction coefficient
bouncenumber0Restitution (bounciness) coefficient
isStaticbooleanfalseIf true, the body does not move
useGravitybooleantrueApply gravity each frame
Access rigidBody.node.physicsBody to call applyForce() and applyImpulse() for manual physics interactions. See the Physics guide for full documentation.

The List Component

List renders a reactive array as a collection of nodes with keyed reconciliation — nodes are created once per key and reused as the array changes. It is imported from fraxel/jsx.
import { List } from 'fraxel/jsx'
import { useSignal } from 'fraxel/hooks'

function EnemyList() {
  const [enemies, setEnemies] = useSignal([
    { id: 1, x: 0 },
    { id: 2, x: 50 },
  ])

  return (
    <List
      array={enemies}
      itemKey={(e) => e.id}
      empty={<text text="No enemies" position={[0, 0]} style={{ foregroundColor: '#aaa' }} />}
    >
      {(enemy) => (
        <transform position={[enemy.x, 0]}>
          <sprite textureId={ENEMY} />
        </transform>
      )}
    </List>
  )
}
List Props
PropTypeDefaultDescription
arrayT[] | SignalGetter<T[]>Required. The reactive array to render
itemKey(value: T, index: number, arr: T[]) => string | symbolRequired. Unique key extractor per item
emptyFraxel.NodeFallback node rendered when array is empty
children(value: T, index: number, arr: T[]) => Fraxel.NodeRequired. Render function called for each item
List produces no wrapper node in the tree — it renders a hidden <transform> as an internal anchor for reconciliation. Removed keys have their nodes destroyed; new keys create fresh nodes. Unchanged keys are left untouched.

Hooks

useNode, useEvent, useSignal, and all other Fraxel hooks

Reactivity

How signals drive reactive prop updates without a Virtual DOM

Physics

Collider shapes, rigid bodies, and collision groups

Animation

Sprite sheet keyframes and AnimationPlayer usage

Build docs developers (and LLMs) love