Skip to content

three-flatland

The composable 2D library for Three.js

2D primitives for three.js and react-three-fiber. ECS-driven sprite batching with TSL effect composition.

Features

Sprites

Instanced Atlas + per-instance animation

Sprites should be cheap enough to scatter without counting them. Sprite2D and AnimatedSprite2D join an instanced batch on scenegraph entry. The spritesheet loader speaks TexturePacker; the animation system drives per-instance playback.

Effect-aware batching

ECS Dynamic batch archetypes

An ECS packs sprite state into compact GPU buffers, so per-sprite effects share one material without fragmenting the batch. Tint, outline, dissolve, palette swap — written in TSL, branch-pruned at compile time.

React Three Fiber

v10 Alpha Three.js >= 0.183

Every primitive ships a typed JSX wrapper — <sprite2D>, <tileMap2D>, <spriteGroup>, <flatland>. Hooks compose with useFrame and useThree. Compatible with react-three-fiber v10’s alpha WebGPU canvas, mounts inside <Canvas> like any other element.

Pixel-art rendering

Retro Pixel-art shader effects

The retro effect category — CRT scanlines, dithering, pixelation, palette quantization — runs as per-sprite TSL nodes. TextureLoader applies pixel-art presets: nearest-neighbor filtering, no mipmaps, no subpixel bleeding.

Composable TSL effects

50+ Composable shader effects

Pixelate, dither, CRT scanlines, dissolve, palette swap, chromatic aberration — a TSL effect library composed per-sprite via a typed schema. Override the color node to drop in your own.

Chunked tilemaps

Chunked Per-chunk visibility

TileMap2D batches like sprites, with chunks instead of per-tile draws — only visible chunks render. Loads from Tiled or LDtk JSON, with multiple layers, animated tiles, and collision data.

Quick start: 2D in three.js

import { Sprite2D, SpriteGroup } from 'three-flatland'
// Create a sprite group and add to your scene
const spriteGroup = new SpriteGroup()
scene.add(spriteGroup)
// Create sprites with textures
const player = new Sprite2D({ texture: playerTexture })
const enemy = new Sprite2D({ texture: enemyTexture })
enemy.position.set(2, 0, 0)
// Add sprites to the group
spriteGroup.add(player, enemy)

Built for three.js

Native to the three.js scenegraph

Composing existing 2D rendering libraries with three.js is hard. Pull one in and you’re running a second full renderer that competes for the frame, with GPU resources shared by hand and the integration held together by hacks. Inside react-three-fiber the cost doubles: another reconciler, state marshaled across two React instances.

three-flatland is built directly on three.js. There’s no parallel renderer to coordinate, no bridge between React instances to maintain. three.js is the renderer, react-three-fiber is the reconciler, and every 2D primitive — Sprite2D, TileMap2D, SpriteGroup — is a Three.js object that mounts into the scenegraph the same way a Mesh does.

Sprite batches that survive your effects

Most sprite batchers fall apart the moment a single sprite needs special treatment. Changing a uniform tanks the batch; shifting a z-index forces a scenegraph reorder; attaching an effect reflows the scene around it.

three-flatland’s batcher composes effects on a single shared material — adding a glow to one sprite doesn’t fragment the batch or rebuild the shader. The cost stays proportional to what’s actually used: the uber-shader prunes unused branches at compile time, and the ECS keeps batch archetypes optimal as effects appear and disappear. To customize beyond the defaults, swap the color node or write a new effect against the same schema.

The ecosystem is three.js

Flatland doesn’t define a secondary ecosystem inside three.js — three.js is the ecosystem. Each @three-flatland/* package follows a Unix-style one-job rule, is tree-shakable, and works in any three.js project, with or without Flatland.

@three-flatland/skia brings Skia to Three.js — with a native WebGPU backend (Graphite/Dawn) that CanvasKit doesn’t ship, plus a Zig-built WASM bundle tuned for size and speed. A TSL port of the Slug glyph-rendering algorithm is in flight — fully shader-driven, accurate at any scale, with real dynamic kerning instead of bitmap atlas sampling. A KTX2 Basis transcoder and runtime encoder (WASM + SIMD) and a VS Code plugin for 2D web projects are in development.

When not to reach for Flatland

If your only 2D need is in-canvas app UI (buttons, panels, settings menus, dialogs), uikit is purpose-built for that. Flatland isn’t.

For sprites, tilemaps, particles, shaders, HUDs, data viz, hybrid 2.5D — that’s three-flatland’s territory. Every @three-flatland/* package stands on its own, so adopting Flatland is incremental, not all-or-nothing.