API reference

Introduction

For a runnable category/detail setup, begin with Getting started. This reference describes the current public API.

Ownership: which setting belongs where?

OwnerPurposeDefined in
SpatialElementDataOne element's content, model, lighting and detail presentationApplication data / CMS, consumed by page load functions
SpatialThemeShared brand identity, colors and panel materialsPersistent layout, usually via createSpatialTheme
BrandStageShellRenderer lifetime, interface translations, GPU budget and decoder infrastructurePersistent +layout.svelte
ContentPageDataCategory title, introduction and breadcrumbsCategory +page.svelte or its load data
SpatialListItemDerived link, summary and assets for an element occurrencegetSpatialListItems(elements, basePath)
Section definitionAnchor/navigation label, optional visible headline, background and local CSS overridesContent/detail page

There is no separate SpatialStageConfig document. The adapter derives its internal renderer scene from the active page. You do not need a layout-level catalog, an active element ID, or a duplicate list of models.

Element data

All fields are flat. See SpatialElementData TSDoc.

FieldRequired/defaultMeaning
idRequiredStable identity within the theme namespace; use URL-safe IDs.
titleRequiredVisible title and default document title.
geometryRequiredBoth low and high: model URL strings, or versioned resource descriptors from an asset pipeline. Low for categories/transitions; High for detail. Same URLs share a cached fetch/decode.
hdrRequiredDetail lighting and background HDR. Category lighting is set independently on ContentPage.hdr.
brandIdInherited from shellOptional assertion: must equal theme.id when supplied.
pageTitletitleBrowser document title.
eyebrowEmptyShort series/category label.
features[]Summary entries: { label, marker? }.
actionAbsent{ label, href?, ariaLabel?, semanticId? }. With href renders a link; without it renders a button. The host owns business behavior.
background{ blurriness: 0.2, tint: '#b6deff', tintIntensity: 0.3 }HDR background presentation; independent of model lighting and theme.background. Supply the full settings object when overriding.
modelImported pose/materialsOptional rotation {x,y,z} in radians, excludeMeshes (exact mesh names), materialOverrides (physical material settings keyed by mesh name). Excluded context meshes remain visible on the main stage but do not determine fit, picking, outlines or minimaps.
cameraAutomatic fitazimuth/elevation in degrees, fitPadding in CSS pixels, fitScale as a multiplier. On detail pages, enlargement may consume padding but stays inside the media fit rectangle so the media rail and summary remain clear.
fallbackImageAbsentPoster URL for loading, SSR and unavailable WebGPU. Recommended.
fallbackImageSizeAbsentActual poster pixel dimensions [width,height], not physical model dimensions.
breadcrumbs[]{ label, href? }; entries without href are plain text.
mediaOne minimapEntries with id, kind and label; kinds minimap, image, video, youtube. Detail pages always place one 3D/minimap entry first, including when media: [] is supplied. See media and input for fields and lifecycle. The old source-less drawing entry is deprecated; replace it with an image.
minimapRuntime fit defaultsPer-element inset, modelScale, hoverModelScale, expandedHoverModelScale and dockedView. Shared colors belong in the theme.
assetManifestAbsentAdvanced verified resource manifest. Not needed for a simple GLB.

Category pages require an explicit ContentPage.hdr. No element supplies or overrides the category environment, including the first element. Detail pages use the selected element's hdr.

An action without href does not install a click handler automatically. Use ordinary links or application-owned controls in Section children for custom application behavior. Theme and content options do not perform purchases, fetch content or own routing.

Model/camera/minimap types document units and supported material properties. LOD and resource ownership explain the advanced asset contract.

Geometry details

ts
geometry: { low: '/assets/chair-low.glb', high: '/assets/chair-high.glb' }
// A tiny model can serve both roles, with one cached fetch and decode:
geometry: { low: '/assets/cube.glb', high: '/assets/cube.glb' }

Both roles are mandatory. URLs identify resources independently of the Low/High role. Sharing a file does not produce an optimized version automatically. Distinct exports must share origin, axes and scale; there is no automatic alignment or decimation. Without generated metadata, Low bounds determine the shared model fit.

Advanced pipelines can provide resource objects {url, format, revision, requirements?} instead of strings, plus geometry.assetToFrame, bounds, revision and status. For the same URL, resource metadata must agree; contradictory versions are rejected instead of downloading the same URL twice. Version changing content with a URL or resource revision. geometryFromLodPair converts existing generated pair metadata without carrying its redundant sourceUrl into authored element data. See geometry TSDoc.

Shared theme

Themes can change at runtime. Pass the selected theme and optional resolved colorScheme="light" or "dark" to BrandStageShell. Optional theme.sceneBackground overrides the visible HDR backdrop without changing model lighting. The host owns preference, storage and system detection. See Runtime themes and ButtonGroup for complete examples and the generic ButtonGroup API.

Use createSpatialTheme({ id, name, ...overrides }). Only id and name are required; each appearance group merges field-by-field with defaults. Every call returns independent objects. Existing complete SpatialTheme objects remain valid.

GroupPurpose and helper defaults
id / nameStable collection identity, independent of URL structure / accessible brand name.
backgroundFlat page color: #eef1f4. This is not the HDR background.
sceneBackgroundOptional partial overrides for the element's HDR backdrop: blurriness, tint, tintIntensity. Leaves model lighting unchanged.
colorsink #173047, body #263c4d, accent #285c89, onAccent #ffffff, tabBackground #e4ebf1.
panelShaperadius 16 and contentInset 26, both in CSS pixels.
radiicontrol 12px for cards/small controls; panel 16px for panels/detached section groups. An explicitly authored panelShape.radius is the compatibility fallback for both roles; explicit radii fields win.
sectionBackgroundsstandard #ffffff and muted #f5f7f9, independent of glass/frosted panel material colors.
panelThemesurface frosted, tint #ffffff, tintOpacity 0.65, backdropBlur 8px, shadowIntensity 0.2, opacity 1. Further glass controls use runtime defaults.
dockedPanelThemeCompact header/tabs: tint #ffffff, tintOpacity 0.92, backdropBlur 5px, shadowIntensity 0.2.
sectionThemeLegacy section material: tint #ffffff, tintOpacity 0.8, backdropBlur 8px. New section.background roles use sectionBackgrounds instead.
minimapThemeexpandedHeight 240px, overlayColor #000000, overlayOpacity 0.35, overlayBlur 5px, contextOpacity 0, viewportColor from accent.
interactionThemeoutlineColor from accent; other picking/outline controls use runtime defaults.

Color overrides do not modify GLB materials. Use element.model.materialOverrides for that. Opacity and tint strengths use 0..1. Panel surfaces are solid (CSS), frosted (CSS backdrop blur) and glass (GPU refraction). Glass can cost more GPU work; the helper chooses frosted.

radii and sectionBackgrounds are optional on older manually constructed SpatialTheme objects; the factory resolves both groups. Radius values are finite, nonnegative CSS pixels: negative values clamp to zero and nonfinite values use the resolved fallback. A radius does not change contentInset.

See createSpatialTheme, theme types, panel controls and renderer controls for all fields.

Interface language is independent of the theme. Pass messages: SpatialMessagesInput to BrandStageShell to override control labels, accessible names, tooltips and 3D-help instructions. Missing keys use English defaults. See interface texts and help for all keys and a complete translation example.

Svelte components

ComponentProps and responsibility
BrandStageShellRequired reactive theme; optional colorScheme (light/dark), messages, renderSettings, dracoDecoderPath, deviceOrientation (default false, for detail geometry/minimap), items: MenuItem[], brand/tools/menu snippets, left/right satellite snippets (Settings below XL), summary: SpatialSummarySnippet, and children. Persistent layout and renderer owner. No stage/catalog props.
ButtonGroupRequired items, label and shape; controlled value/onchange, optional theme, iconOnly and content snippet. Generic panel-backed choices owned by the host.
ContentPageRequired hdr: environment URL and page: ContentPageData; optional background: CSS color and children. background overrides the flat page color for this page in DOM and enhanced rendering, without changing HDR lighting or the shared theme. page.title is required; pageTitle defaults to title. Place visible headings and introductory text in child sections. Use leftSidebar, footer and showContentMenu for navigation.
SpatialCardGridSectionRequired section: {id,title,headline?,background?,style?}, list: SpatialListItem[]. Optional cardSize, cardVariant, cardTone, mediaAspect, mediaFit, textAlign and itemSpan; see Cards below. Registers its own element occurrences.
SpatialCarouselSectionSame section/list; optional presentation: {radius?,depth?}, initialItemKey, onselectionchange({itemKey,spatialElementId}). Radius controls model spacing, normalized around 0.48 (clamped 0.2..0.7); depth defaults to 700 CSS-world pixels (clamped 0..1200). The selected media fit and summary placement follow the shared responsive layout. Models, shadows and hit areas are clipped to the section surface: standard stays within the section; full extends horizontally to the viewport.
SpatialListPageConvenience wrapper: ContentPageData fields directly as props, required hdr and spatialElements, optional sectionLink: {href,label}. For new composable pages prefer ContentPage with sections.
SpatialElementPageRequired spatialElement: SpatialElementData; optional children. Owns detail content, model selection and generated section navigation.
SectionRequired section: {id,title,headline?,background?,style?}; optional children and HTML attributes/class. The presentation prop is used by spatial list/carousel wrappers. Detail sections automatically populate tabs.
ContentGridOptional children and HTML attributes/class. Places explicit ContentBlock children on the enclosing section's content grid; adds no outer padding.
ContentBlockOptional span: number or {xs?,s?,m?,l?}, children and HTML attributes/class. Fills the row by default; responsive spans inherit mobile-first.

Mount one active ContentPage or SpatialElementPage under each shell. Sections are children of that page; they are not independent pages. Section IDs must be stable, unique and start with a lowercase letter, followed by lowercase letters, digits or hyphens.

Carousel layout and summary content

Carousel sections use the ordinary content grid, heading gap and background rules. The selected media fit is a framing target, not a clipping boundary for neighbouring geometry. XS/S reserve a 66px title strip and show only the framework's title, optional eyebrow and detail arrow; without an eyebrow the 40px title block aligns to the bottom of that same strip. From M, the full summary has the matching detail-panel width and centers vertically in the fit area. Its height is intrinsic, capped at the fit height with local scrolling. The 3XL carousel keeps the 2XL composition and 480px panel width.

Define optional BrandStageShell.summary in the persistent brand layout. The exported SpatialSummarySnippet receives a SpatialSummaryContext containing common resolved spatialElement fields, mode: 'detail' | 'carousel', heading, contentInset and catalog endpoint addresses. The default summary remains available when the snippet is omitted. Each summary has an inline-size query container named spatial-summary; use container queries for host-defined one-, two- or three-column content. Initial output must be synchronous and content-only; await required data in route load, and keep svelte:head outside the summary.

The adapter distinguishes occurrences by section and itemKey. A product shown in two sections has two independent live summary instances. Carousel selection and responsive changes retain each occurrence's interactive state. Route-transfer activation belongs to the transition coordinator; matching a product ID alone is not sufficient to adopt another occurrence. See summary rendering and ownership for the precise lifecycle and SSR boundaries.

Section headings and surfaces

section.title names the section in generated navigation and for accessibility. Only optional section.headline renders a visible heading. Omitting it also removes its heading gap; the section keeps an accessible label. Set headline: title explicitly when retaining an old visible heading.

section.background is { color, size? }; omitted backgrounds resolve to transparent/standard. Color values are transparent, standard, muted, transparentToStandard, transparentToMuted, standardToTransparent, standardToMuted, mutedToTransparent and mutedToStandard. Size is standard (default, the padded grid width) or full (viewport-width background with the same content grid). Gradients run from top to bottom and require effective full size. Detail pages constrain every background to standard. A gradient that is not effectively full becomes its end color and emits a development diagnostic once per offending configuration/context.

Detached, adjacent opaque standard-width sections share outer panel-radius corners on wide layouts; transparent and full-width sections break the group. Different standard/muted colors do not break it. The 3XL detail ContentPane owns its outer corners instead of rounding its individual sections.

Legacy section.style still accepts tint, tintOpacity, backdropBlur, textColor and CSS width. Keep explicit legacy material settings when migrating a custom surface; do not add a new background role on top of them and expect both material systems to apply. Prefer the new grid/container contract to independent section widths, and retain textColor only as an orthogonal text override.

Content grid and cards

Grid classification uses the content container width before section padding, capped at 1536px. It is independent of viewport classification. Section owns one outer padding on each side; ContentGrid uses the same token for its inter-block gap.

Container widthGridColumnsPadding/gapHeadline size/line heightBody size/line height
Below 600pxxs412px18px/28px14px/20px
600px to below 840pxs616px18px/28px14px/20px
840px to below 1200pxm816px20px/32px14px/20px
1200px to 1536pxl1224px24px/40px16px/24px

ContentBlock span values are positive integers. A single number applies at every grid size. Responsive values inherit the last supplied smaller size; before the first supplied value they fill the row. Invalid values fall back to the full row and excessive spans clamp to available columns, with development diagnostics. Placement follows DOM order without dense backfilling. Ordinary Section children remain ordinary content unless wrapped in ContentGrid/ContentBlock.

Cards use Page Sections 0.4.1. The media frame has its own configurable aspect ratio; the text area adds independent height. All cards in a collection have equal heights across rows.

  • S: 2 columns, 12px title, two title lines, no eyebrow.
  • M: 2 columns, 14px title, two title lines.
  • L: 4 columns (3 in grid S), 18px title, three title lines.
  • M/L reserve an additional eyebrow row for every item when any item has a nonblank eyebrow.
  • Text padding is 12px; contained media has an 8px inset.

Use these options directly on SpatialCardGridSection (not inside section): cardSize="s|m|l", cardTone="standard|muted", textAlign="start|center", mediaAspect="1/1|3/2|4/3" and mediaFit="contain|cover". SpatialListPage forwards all of them. cardVariant="catalog" (default) uses responsive size-based spans; cardVariant="editorial" uses itemSpan (default { xs: 4, s: 3, m: 4, l: 4 }) and a title link. Spatial media defaults to 1/1; Page Sections defaults to 3/2. Keep itemKey stable for repeated items and reordering.

See Page shell and navigation for composition and route conventions.

getSpatialListItems(elements, basePath) derives links and includes summary and flat assets automatically. List entries can customize summary.sectionLink to point at an authored detail section; this optional link sits below the carousel surface and does not change the shared property panel's content or height. Use itemKey to distinguish repeated copies of the same element within one section; internal pose/occurrence fields are assigned by the adapter. findSpatialElement(elements, id) returns the matching document or undefined; the host decides how to render a 404.

Keep ordinary anchor links and supply all required data through route load functions. Transition matching and deployment requirements are documented in Getting started.

SpatialListItem.targetPresentation: 'dock' and summary.sectionLink.targetPresentation: 'dock' declare that a fragment destination opens the completely docked content view. This optional contract allows its endpoint to be resolved before route mounting; arbitrary fragments still use the ordinary restored-destination path. Native document limits remain authoritative. In 3XL the model stays on the fixed left stage and the pane header is the corresponding endpoint. See known detail endpoints.

The source-first transition currently requires ?stagedNavigation=1 on the source page and an enhanced, prepared source. It remains an experimental opt-in until the production performance gate is accepted. The declaration does not enable that path by itself. Without the query opt-in, the ordinary transition remains active; no application link or history migration is required.

getSpatialListItems also preserves each element's minimap settings so a compact destination uses the same fit, pivot and docked orientation as its mounted detail. Custom synchronous shell summaries receive SpatialSummaryContext. Initialize createCatalogEndpointAction() inside the custom summary component and apply context.address('title') / context.address('eyebrow') to its actual text nodes. The factory depends on the enclosing Stage context and cannot be initialized by a parent above BrandStageShell. See the complete live summary example.

Renderer infrastructure

BrandStageShell.renderSettings accepts maxPixelRatio (default 1) and maxPixels (default 2,073,600). The tighter budget wins. maxPixels: null disables the area limit; invalid/nonpositive numeric values fall back to defaults. DOM text retains native resolution. Budget settings are applied at renderer creation and page binding; they are not an animation control. See performance.

BrandStageShell.dracoDecoderPath is the directory of host-served Draco decoder files, with trailing slash. Default: /assets/draco/gltf/. Models and decoders are not application static assets installed automatically by the package. Meshopt uses the Three.js decoder dependency.

Stage, Panel, StageViewport and StageExperience are lower-level building blocks for custom integrations. Stage's low-level standalone glb/hdr/model/background/camera options configure a manually composed scene; they do not need to be repeated when using BrandStageShell and page components. Manual Stage.catalog / CatalogPage is retained as a legacy adapter contract. New SvelteKit consumers should use page registration through the high-level components. Internal SpatialElementScene is a renderer projection, not a third authoring document.