Gesso
Brand

Relief

Generated isometric illustration, with extruded outlines stacked and exploded and colored by role.

Relief builds isometric technical illustrations from data rather than from artwork. A shape is extruded from a vector outline, layers stack into a block, blocks are placed into an assembly, and the whole thing comes apart along authored vectors. Nothing is drawn by hand and nothing is imported, so the illustration is correct at every angle, follows the theme, and stays right when the source changes.

The argument for generating it is the same one that applies to any hand-drawn depth: a rounded box faked with a top face, two side faces, and corner slivers is correct at exactly one viewing angle and stale the moment anything moves.

Render style

The model

Three tiers, each nesting inside the next.

TierWhat it is
LayerOne extruded outline, with a source, a depth, an explode offset, and a color role.
BlockA group of layers with its own internal explode.
AssemblyBlocks placed in space, with an explode of its own that fans whole blocks apart while their internals stay sealed.

The two explode axes are independent on purpose: layers stack, but blocks need to fan sideways and rearrange.

Three styles, one geometry

outline is the house style, a flat face with depth carried by line work, which is what hand-drawn isometric art was imitating in the first place. solid shades the extrusion, and wire draws the construction with nothing filled in. All three read the same geometry.

Color follows the theme

A layer names a role, never a value. color: "surfaceRaised" resolves against whichever look and color scheme the scene is sitting in, so one assembly repaints across every theme without a second definition. Fills take surface roles only, and brand color lives on edges, connectors, and glow, never as a field.

Color can climb a ladder, but only on line work. Pass edgeLadder and a layer picks its hairline role by the same value the geometry encodes, so brand color arrives at an edge at the loud end of a reading and never as a field. Fills stay on surface roles no matter what.

The one thing that stands outside the theme is a mark. A layer may carry fill, a CSS hex, for a logo whose color is the brand rather than the look; its hairline takes the same color unless edge names a role.

Materials cannot read a CSS custom property, so roles resolve at render through a probe element inside the scene's own container. That scoping is what makes two differently themed scenes on one page work with no extra machinery.

Usage

import { ReliefScene } from '@/components/relief/scene';

<ReliefScene
  assembly={{
    id: 'slab',
    blocks: [{ id: 'stack', layers: [
      { id: 'base', source: { kind: 'roundedRect', width: 320, height: 200, radius: 20 },
        depth: 12, color: 'surfaceSunken' },
      { id: 'top', source: { kind: 'roundedRect', width: 280, height: 160, radius: 16 },
        at: { z: 12 }, exploded: { z: 90 }, depth: 12, color: 'surfaceRaised' },
    ] }],
    placements: [{ id: 'only', blockId: 'stack' }],
  }}
  explode={{ assembly: 0.5 }}
  label="A two-layer slab, half exploded"
/>

Accessibility

  • The canvas is aria-hidden unless you pass label, which promotes it to role="img". Decorative geometry should stay hidden; an illustration carrying meaning needs the label.
  • Relief hangs no labels on its geometry. Copy belongs to the stage as one subtext line per beat, which doubles as that beat's accessible description.
  • Nothing in the scene is focusable or interactive, so there is no keyboard surface to trap.
  • Explode is a value you set, not an animation that runs on its own.

Install

shadcn add @gesso/relief

On this page