A CSS polygon mesh engine. A 3D renderer for the DOM. Renders OBJ, glTF, GLB and VOX as real HTML elements transformed with CSS matrix3d(...). Supports colors, textures, lighting, shadows, controls, shapes and animations. Works with React, Vue, custom elements, or plain JavaScript.
Visit polycss.com for docs and model examples.
# React
npm install @layoutit/polycss-react
# Vue
npm install @layoutit/polycss-vue
# Vanilla / custom elements
npm install @layoutit/polycssYou can also load polycss directly from a CDN. Here is a minimal custom-element scene:
<script type="module" src="https://esm.sh/@layoutit/polycss/elements"></script>
<poly-camera rot-x="65" rot-y="45">
<poly-scene>
<poly-orbit-controls drag wheel></poly-orbit-controls>
<poly-box size="100" color="#ffd166"></poly-box>
</poly-scene>
</poly-camera>React and Vue expose the same component model. <PolyCamera> owns the viewpoint, <PolyScene> owns lighting and atlas options, and <PolyMesh> loads or receives polygon data.
import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/polycss-react";
export default function App() {
return (
<PolyCamera rotX={65} rotY={45}>
<PolyScene textureLighting="dynamic">
<PolyOrbitControls drag wheel />
<PolyMesh src="/gallery/obj/cottage.obj" mtl="/gallery/obj/cottage.mtl" />
</PolyScene>
</PolyCamera>
);
}rotX,rotYcontrol the orbit angle in degrees.zoomscales the projected scene.targetpans the camera target in world coordinates.distanceadds dolly pull-back.PolyCamerais the orthographic default. UsePolyPerspectiveCamerawhen you want perspective depth.
polygonsrenders a staticPolygon[]directly.directionalLightandambientLightcontrol scene lighting.textureLightingchooses"baked"or"dynamic".textureQualitycontrols atlas raster budget.strategiescan disable selected render strategies for diagnostics.autoCenterrotates around the rendered mesh bounds instead of world origin.
srcloads.obj,.gltf,.glb, or.voxfiles.mtlloads companion OBJ materials.polygonsaccepts pre-parsed geometry.position,scale, androtationtransform the mesh wrapper.autoCentershifts the mesh bbox center to local origin.meshResolutionchooses"lossy"(default) or"lossless"optimization.castShadowemits CSS-projected shadows in dynamic lighting mode.
<PolyOrbitControls>adds drag orbit, shift-drag pan, wheel zoom, and optional auto-rotate.<PolyMapControls>uses pan-first map-style input.<PolyFirstPersonControls>provides keyboard and pointer-look navigation.<PolyTransformControls>adds translate/rotate gizmos for selected mesh handles.
Each polygon describes one renderable face:
const polygons = [
{
vertices: [[0, 0, 0], [60, 0, 0], [0, 60, 0]],
color: "#f97316",
},
{
vertices: [[0, 0, 0], [60, 0, 0], [60, 60, 0], [0, 60, 0]],
texture: "/texture.png",
uvs: [[0, 0], [1, 0], [1, 1], [0, 1]],
},
];Render polygons directly when you need per-face DOM events or custom styling:
<PolyCamera>
<PolyScene>
{polygons.map((polygon, index) => (
<Poly
key={index}
{...polygon}
onClick={() => console.log("clicked polygon", index)}
className="my-polygon"
/>
))}
</PolyScene>
</PolyCamera>Use loadMesh() to parse supported model formats:
import { createPolyCamera, createPolyScene, loadMesh } from "@layoutit/polycss";
const host = document.getElementById("polycss")!;
const camera = createPolyCamera({ rotX: 65, rotY: 45 });
const scene = createPolyScene(host, { camera });
const mesh = await loadMesh("https://polycss.com/gallery/obj/cottage.obj", {
mtlUrl: "https://polycss.com/gallery/obj/cottage.mtl",
});
scene.add(mesh);Supported formats:
- OBJ + MTL, including
map_Kdtextures and UV coordinates. - glTF / GLB, including embedded images and
TEXCOORD_0. - MagicaVoxel
.vox, with direct voxel fast paths when eligible. - Generated primitives: box, plane, ring, sphere, torus, cylinder, cone, and Platonic solids.
polycss renders in the DOM, so performance is mostly determined by how many polygons are mounted and how much texture atlas area they consume. The renderer uses several CSS strategies so simple surfaces stay cheap and textured or irregular surfaces fall back to atlas slices.
- One visible polygon becomes one leaf DOM element.
- Flat rectangles and stable quads use solid CSS leaves.
- Textured polygons are packed into generated texture atlases.
- Dynamic lighting runs through CSS custom properties instead of per-frame JavaScript.
- Voxel-shaped meshes mount only camera-facing leaves when the mesh is eligible.
meshResolution: "lossy"can merge compatible polygons to reduce DOM node count.
| Package | Description |
|---|---|
@layoutit/polycss-core |
Pure math, parsers, lighting, camera helpers, mesh optimization. Zero browser globals. |
@layoutit/polycss |
Vanilla custom elements and imperative createPolyScene API. |
@layoutit/polycss-react |
React components, hooks, controls, and core re-exports. |
@layoutit/polycss-vue |
Vue 3 components, composables, controls, and core re-exports. |
Layoutit Voxels -> A CSS Voxel editor
Layoutit Terra -> A CSS Terrain Generator
MIT.



