Almost every time someone says "3D in the browser," what follows is WebGL: a render loop, shader compilation, a canvas you cannot inspect, and a mental model borrowed from game engines. That is the right tool for a lot of jobs, and a heavy one for many others. If all you want is a crisp isometric illustration, a generative art piece, a little block diagram, or artwork you can send straight to a pen plotter, dragging in a full 3D engine feels like bringing a forklift to carry a teacup.
heerich takes the other road. It is a tiny, zero-dependency engine that constructs 3D voxel scenes, blocks on a grid, and renders them to plain <svg>. No canvas, no WebGL, no render loop. The output is just vector polygons you can open in DevTools, theme with CSS, scale to any resolution without blurring, and print. The library is named after Erwin Heerich, the German sculptor known for minimalist geometric cardboard sculptures, and that lineage shows: the whole thing is about stacking clean modular forms and looking at them from a flattering angle.
It comes from David Aerne (meodai), who you may know from generative color tools like color-names and poline, and it shows the same taste for small, focused, well-shaped APIs. At around 10.6 KB gzipped with zero dependencies, it is the kind of library you can adopt without a second thought about bundle weight.
What You Get in 10 Kilobytes
For something this small, the feature surface is surprisingly complete:
- Voxel modeling with CSG-like boolean operations. Build with
union, carve withsubtract, keep overlaps withintersect, or XOR withexclude. Real constructive solid geometry on a grid. - Four camera projections. Oblique (the default cabinet/pixel-art look), perspective, orthographic, and an isometric preset, all switchable through one shared
angleparameter. - Primitive shapes plus a procedural escape hatch. Box, sphere, and line cover the common cases; a general-purpose
fillshape lets you define any form as a(x, y, z) => booleantest function. - Per-face and procedural styling. Style each of the six faces independently, or pass functions of
(x, y, z)for gradients and procedural coloring. Decals let you stamp warped SVG paths onto faces. - Plotter-grade occlusion culling. A built-in clipper produces zero overlapping vectors, which is exactly what pen plotters need.
- Interactivity for free. Every polygon carries
data-voxelattributes, and there is a hit-testing helper to map a screen click back to a voxel and face. - A GPU bridge. When you outgrow SVG,
GPURendererhands you typed arrays ready for Three.js or raw WebGL/WebGPU.
A quick honesty note: heerich is still pre-1.0 (currently 0.14.0) and iterating fast. The API is already rich and pleasant, but expect it to keep moving, so pin your version and read the changelog before upgrading.
Getting It Installed
npm install heerich
yarn add heerich
It ships as ESM-first with a UMD build and bundled TypeScript types. In a <script> tag the UMD build exposes a global Heerich. Everything below is TypeScript.
Building Your First Block House
The core loop is simple: create an engine, add and remove geometry, then call toSVG(). Here is a tiny house, walls, a red roof tint, and a carved-out doorway.
import { Heerich } from "heerich";
const h = new Heerich({
tile: 40,
camera: { type: "oblique", angle: 45, distance: 15 },
});
// The body of the house
h.applyGeometry({
type: "box",
position: [0, 0, 0],
size: [5, 4, 5],
style: {
default: { fill: "#e8d4b8", stroke: "#333" },
top: { fill: "#c94c3a" },
},
});
// Carve out a doorway
h.removeGeometry({
type: "box",
position: [2, 1, 0],
size: [1, 3, 1],
});
document.body.innerHTML = h.toSVG();
A few things are worth unpacking. tile is the pixel size of a single voxel. The style object keys (default, top, and friends) are face names, so the roof gets its own color while every other face inherits default. And removeGeometry is just a friendly shortcut for a subtract operation: it deletes the voxels that overlap the box you describe.
One coordinate quirk to internalize early: in Heerich, Y increases downward, because it inherits SVG screen space. Setting a voxel at y: -4 places it above the origin, not below. The valid coordinate range is -512 to 511 on each axis, which is a generous grid for vector work.
Carving, Combining, and the Four Cameras
The boolean modes are where voxel modeling gets fun. Instead of placing every block by hand, you sketch a solid and then subtract detail out of it, the same way you would model in CAD.
// A solid block
h.applyGeometry({ type: "box", position: [0, 0, 0], size: 6 });
// Hollow it with a sphere
h.applyGeometry({
type: "sphere",
center: [3, 3, 3],
radius: 2.5,
mode: "subtract",
});
// Keep only what also lives inside a second box
h.applyGeometry({
type: "box",
position: [1, 1, 1],
size: 4,
mode: "intersect",
});
When you subtract, you can hand the operation a style to paint the freshly exposed inner "walls" a different color, which is a small touch that makes carved scenes read as solid rather than hollow.
The camera is just as flexible, and you can change it at any time with setCamera. The same scene reads completely differently depending on projection:
// Classic cabinet / pixel-art look (the default)
new Heerich({ camera: { type: "oblique", angle: 45, distance: 15 } });
// True isometric diamond grid (pitch locked to 35.264 degrees)
new Heerich({ camera: { type: "isometric", angle: 45 } });
// One-point perspective with an explicit camera position
new Heerich({ camera: { type: "perspective", position: [5, 5], distance: 10 } });
// Custom dimetric / trimetric views
new Heerich({ camera: { type: "orthographic", angle: 45, pitch: 35.264 } });
The angle parameter is deliberately shared across all four types so you can swap projections without rewriting your config, though it means a slightly different thing in each: depth direction for oblique, horizontal pan for orthographic and isometric, and camera position for perspective. For isometric specifically, the angles 45, 135, 225, and 315 line edges up to the pixel grid, which is what you want for crisp game-style assets. Note that orthographic and isometric are parallel projections, so distance has no effect there.
Procedural Shapes With the Fill Primitive
Boxes and spheres are convenience wrappers. The real engine underneath is fill, which builds whatever shape a test function returns true for. This is the closest thing the library has to a voxel shader.
// A torus, described purely by math
h.applyGeometry({
type: "fill",
bounds: [
[-8, -3, -8],
[8, 3, 8],
],
test: (x, y, z) => {
const R = 6;
const r = 2;
const q = Math.sqrt(x * x + z * z) - R;
return q * q + y * y <= r * r;
},
});
Pair that with functional styling and you get fully data-driven scenes. Style values can themselves be functions of (x, y, z), so a single call can paint a gradient across the whole grid:
h.applyGeometry({
type: "box",
position: [0, 0, 0],
size: 8,
style: {
default: (x, y, z) => ({
fill: `hsl(${x * 40}, 60%, ${50 + z * 5}%)`,
stroke: "#222",
}),
},
});
The same idea extends to scale, where each voxel can shrink along any axis based on its position (handy for bar-chart-style towers or wave fields). Scaled voxels automatically become non-opaque so the blocks behind them show through.
Sending Clean Vectors to a Pen Plotter
This is the feature that makes Heerich genuinely special rather than just convenient. By default the SVG relies on the browser's painter's algorithm: faces are drawn back-to-front and the front ones simply cover the ones behind. That looks correct on screen but leaves overlapping geometry in the file, which a pen plotter would happily redraw line over line.
Flip on occlusion culling and the engine clips hidden geometry away entirely:
const svg = h.toSVG({ occlusion: true });
Now no vector overlaps another, so an AxiDraw-style plotter draws each edge exactly once. The built-in clipper assumes convex occluders, which is perfect for oblique projection and can show minor artifacts in perspective. If you need exact clipping there, you can drop in the polygon-clipping library through the resolveOcclusion hook, but for most isometric work the built-in path is all you need, and it keeps the zero-dependency promise intact.
Interactivity Without a Framework
Because the output is real DOM, interactivity is mostly already there. Every polygon is emitted with attributes like data-voxel="x,y,z", data-face="top", and any custom meta you attached, so you can target voxels with plain CSS or event delegation. When you need to go from a pixel to a voxel, the engine includes a hit tester:
svgEl.addEventListener("mousemove", (e) => {
const pt = svgEl.createSVGPoint();
pt.x = e.clientX;
pt.y = e.clientY;
const { x, y } = pt.matrixTransform(svgEl.getScreenCTM().inverse());
const hit = h.findByPosition([x, y]);
if (hit) {
console.log(hit.voxel.x, hit.voxel.y, hit.voxel.z);
console.log(hit.face.type); // "top", "front", "left", ...
}
});
Combine findByPosition with findVoxels (filter by meta, coordinate, or style) and getVoxelInfo (projected center and bounds in pixel space) and you have everything you need to build hover highlights, click-to-edit grids, or tooltips, all without a rendering framework. The whole scene also serializes cleanly with toJSON and fromJSON, so saving and loading a build is one line each.
When SVG Is Not Enough
Sometimes you start with crisp vector mockups and later need real lighting, depth, or animation. Rather than make you rebuild the scene, Heerich gives you a GPURenderer that converts your voxel faces into typed arrays ready for the GPU.
import { Heerich, GPURenderer } from "heerich";
import * as THREE from "three";
const engine = new Heerich();
engine.addGeometry({ type: "box", position: [0, 0, 0], size: [4, 4, 4] });
const { position, normal, uv, index } = new GPURenderer().render(
engine.getFaces({ raw: true }),
{ yUp: true }, // convert into Three.js coordinate space
);
const geo = new THREE.BufferGeometry();
geo.setAttribute("position", new THREE.BufferAttribute(position, 3));
geo.setAttribute("normal", new THREE.BufferAttribute(normal, 3));
geo.setAttribute("uv", new THREE.BufferAttribute(uv, 2));
geo.setIndex(new THREE.BufferAttribute(index, 1));
scene.add(new THREE.Mesh(geo, new THREE.MeshStandardMaterial()));
The raw: true flag gives you every neighbor-exposed face without camera culling, which is what a real 3D renderer wants, and yUp flips the coordinate handedness into Three.js space for you. So the library scales down to a 10 KB SVG toy and up to the front end of a proper WebGL pipeline, and you choose where on that spectrum to sit per project.
Should You Reach for It
Heerich.js fills a niche that is more crowded in spirit than in practice. Isometric libraries like obelisk.js and isomer.js can stack blocks, but they lack boolean operations, procedural fills, multiple cameras, and plotter output. Zdog renders 3D-ish vectors beautifully but is built for round, illustrative forms rather than a voxel grid with CSG. Three.js can do everything and weighs accordingly. Heerich sits in the gap: a tiny, zero-dependency, SVG-vector voxel engine with real boolean modeling, four projections, procedural styling, and an honest path out to the GPU when you need it.
If your work involves generative art, isometric game or UI assets, data-driven diagrams, pen-plotter pieces, or any crisp, printable 3D illustration, it is well worth an afternoon. Just remember it is still 0.x: lovely to use today, but pin your version and keep an eye on the changelog. For a library you can read end to end in one sitting, it punches far above its weight.