← Learn

Mapping CSS Box Models and Grid Layouts to 3D Coordinates

Software 4 Senses LLC · Last updated

A 3D layout engine for the web does not need to reimplement CSS — it needs a lawful way to read what CSS decided. The working pattern is to lay the author's own stylesheet out over an offscreen tree of empty stand-in boxes, one per element being laid out and each carrying that element's declared size, measure the result with offset geometry, and map each measured rectangle to a world-space pose through a fixed projection contract of 50 CSS px per world unit. A grid template written for a 2D page then positions boxes in a 3D volume, with the browser's real layout engine doing all of the layout.

The things being positioned are elements of the Scene Document Model: 3D volumes, not HTML elements. A slab being laid out can be a solid body, a bare edge frame, or an empty seat that holds a place in the layout with nothing in it. None of them has a DOM box for CSS to lay out, and writing a bespoke constraint solver to replicate CSS grid, flexbox, margin collapsing, and intrinsic sizing would mean maintaining a second layout engine that can only drift from the browser's. The engine instead derives layout from a stand-in. CSS solves boxes; a solver poses volumes. The reference implementation throughout is the Software 4 Senses 3D Spatial Web Studio ("the Studio" below).

Measurement twin — an engine-derived, never-authored offscreen DOM container holding one empty stand-in box per element being laid out, each carrying that element's declared size, laid out by the author's verbatim CSS and measured with offset geometry. The elements themselves are records in the scene document, not DOM; the twin exists solely so the browser's own layout engine can lay out boxes on their behalf.

Two things the twin is not: it is neither the platform's Shadow DOM nor a hidden state store. It is an offscreen layout-measurement container — CSS lays it out so a pure solver can pose 3D elements, and that is its entire job. State for 3D elements lives with the elements themselves (their records, animation clips, and state machines, whose definitions the scene document saves), never in any hidden DOM.

The measurement-twin pattern

The measurement twin lets real author CSS lay out elements that have no DOM box of their own, by laying out stand-ins for them instead. The Studio's shipped procedure, end to end:

  1. Give each element an empty stand-in box. The stand-in is a plain <div> carrying the element's declared size in CSS px as its width and height — or, for a slab set to adopt its cell, as a minimum width and height, so the stand-in stretches to fill the cell the layout grants it, and overflows that cell if its declared size is larger.
  2. Seat the stand-ins in an offscreen container laid out by the author's own CSS. The stylesheet is applied verbatim and unscoped, with one exception: a bare [data-rig] selector is bound to this rig when the sheet is written, so a layout rule can scope itself without naming the rig. Each stand-in sits inside a persistent, engine-owned cell box. The engine's own stylesheet makes exactly one declaration about that cell (display: grid, placed first in the sheet so an author rule of equal specificity still wins), and beyond its id the only thing ever written on a cell is a seat size the author states for it in the rig definition. Nothing derived — row, column, or a mirrored class — is ever written onto a cell.
  3. Measure with offset geometry. The twin reads offsetLeft/offsetTop/offsetWidth/offsetHeight, summing offsets up the offsetParent chain to its own container, so every rect is container-relative.
  4. A pure solver maps each rect to a world pose. The solver takes measured CSS rects plus the rig definition and returns world-space poses. It imports no DOM and no engine — given the same rects it returns the same poses, which is what makes the layout half testable without a GPU.
  5. Arrangement changes tween FLIP-style from live poses. When the layout changes (a grid becomes a carousel), the target layout is solved instantly and each element is animated from its current live pose to its target as one interruptible group — the classic First-Last-Invert-Play technique lifted into world space. The Studio's shipped default for these transitions is 450 ms with cubic-bezier(0.33, 1, 0.68, 1) — a designed, feel-tested default, not a measurement.

One stability rule rides on top: nothing derived from a measurement — no row, no column, no mirrored class — is ever written back onto a cell, so an author rule keyed on a measured value can never resize a cell, reflow the rig, and re-enter its own input; the measure → pose → measure feedback loop is structurally impossible rather than merely damped.

The projection contract

Three fixed conventions connect a measured CSS rectangle to a world-space pose. All three are contracts the engine holds itself to — conventions, not measurements:

Pitfalls measured in the field

Two failure modes were hit and probed while shipping this pattern; each dictates one of the twin's design choices.

In the Studio

In the Studio this machinery surfaces as the slab/rig layer: a slab is the atomic content-bearing volume of a 3D site (a card in the Studio's vocabulary) — the div's replacement, which may carry a solid body and an edge frame, each optional and neither present by default — and a rig is an arrangement machine (plane, shelf, ring, stack) that positions slabs, the 3D analogue of a flex or grid container, driven by real author CSS through the measurement twin described above. Within the Scene Document Model the rig is the layout machine, and its layout cells are first-class: minted once with ids that are never recycled, cells outlive their occupants — a slab can vacate its seat and another can be seated in the same cell without the layout being rebuilt. The definition layer is guarded by a validator in which unknown keys throw at every level rather than being silently ignored — a typo is an error message, not a field that does nothing — and normalized output revalidates to itself, so a saved scene round-trips through the same schema that authored it. That schema discipline is what keeps a layout system spread across CSS, stand-ins, solvers, and GPU records debuggable: every layer either accepts a definition exactly or says precisely why not.

Related reading