ComposeFeature simplifies Compose panel creationTransform, Pose, and Vector3| Requirement | Details |
|---|---|
StarterSample | Clone from Meta-Spatial-SDK-Samples |
Android Studio | Hedgehog or later |
Target device | Meta Quest 3 or 3S |
StarterSample project folder in Android Studio.
StarterSample/app/src/main/java/com/meta/spatial/samples/startersample/StarterSampleActivity.kt and locate the registerFeatures() method:override fun registerFeatures(): List<SpatialFeature> {
val features = mutableListOf<SpatialFeature>(
VRFeature(this),
ComposeFeature(),
)
// Debug features omitted for clarity
return features
}
ComposeFeature() is already registered, which provides helper functions and classes that simplify working with Compose in panels. For example, ComposeFeature enables the composePanel {} builder syntax used later in this tutorial. Jetpack Compose works in panels without this feature, but ComposeFeature makes common panel patterns more convenient.registerPanels() method:override fun registerPanels(): List<PanelRegistration> {
return listOf(
ComposeViewPanelRegistration(
R.id.panel,
composeViewCreator = { _, ctx ->
ComposeView(ctx).apply { setContent { WelcomePanel() } }
},
settingsCreator = {
UIPanelSettings(
shape = QuadShapeOptions(width = 2.048f, height = 1.254f),
style = PanelStyleOptions(themeResourceId = R.style.PanelAppThemeTransparent),
display = DpPerMeterDisplayOptions(),
)
},
)
)
}
Note: This tutorial usesPanelRegistrationwithcomposePanel {}, which provides simpler syntax for common use cases. The StarterSample usesComposeViewPanelRegistration, which offers more explicit configuration. Both approaches are valid.
StarterSample/app/src/main/res/values/ids.xml and add a new panel hello_panel ID:<?xml version="1.0" encoding="utf-8" ?>
<resources>
<item type="id" name="panel" />
<item type="id" name="hello_panel" />
</resources>
R.id.hello_panel, which you will use to identify your new panel.SpatialTheme wrapper to ensure the panel renders correctly.StarterSample/app/src/main/java/com/meta/spatial/samples/startersample/HelloPanel.kt:package com.meta.spatial.samples.startersample
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp
import com.meta.spatial.uiset.theme.LocalColorScheme
import com.meta.spatial.uiset.theme.SpatialColorScheme
import com.meta.spatial.uiset.theme.SpatialTheme
import com.meta.spatial.uiset.theme.darkSpatialColorScheme
import com.meta.spatial.uiset.theme.lightSpatialColorScheme
@Composable
fun HelloPanel() {
SpatialTheme(colorScheme = getPanelTheme()) {
Box(
modifier = Modifier
.fillMaxSize()
.background(brush = LocalColorScheme.current.panel),
contentAlignment = Alignment.Center
) {
Text(
text = "Hello Spatial SDK!",
fontSize = 32.sp,
color = SpatialTheme.colorScheme.primaryAlphaBackground
)
}
}
}
@Composable
private fun getPanelTheme(): SpatialColorScheme =
if (isSystemInDarkTheme()) darkSpatialColorScheme() else lightSpatialColorScheme()
SpatialTheme wrapper for proper renderingLocalColorScheme.current.panel for the translucent backgroundregisterPanels() in StarterSampleActivity.kt to include your new panel. Add the new registration to the existing list:import com.meta.spatial.compose.composePanel
import com.meta.spatial.toolkit.PanelRegistration
override fun registerPanels(): List<PanelRegistration> {
return listOf(
// Existing welcome panel (keep as-is)
ComposeViewPanelRegistration(
R.id.panel,
// ... existing configuration
),
// New hello panel using simple registration
PanelRegistration(R.id.hello_panel) {
config {
width = 1.5f
height = 0.8f
enableTransparent = true
themeResourceId = R.style.PanelAppThemeTransparent
}
composePanel {
setContent { HelloPanel() }
}
}
)
}
| Option | Description |
|---|---|
width / height | Panel dimensions in meters |
enableTransparent | Allows transparency in the panel to show the 3D scene behind it |
themeResourceId | Android theme for the panel window |
PanelRegistration provides a simpler alternative to ComposeViewPanelRegistration. The config {} block sets panel dimensions and styling, while composePanel {} defines the Compose content.onSceneReady() method in StarterSampleActivity.kt and add the panel entity creation:import com.meta.spatial.core.Entity
import com.meta.spatial.core.Pose
import com.meta.spatial.core.Quaternion
import com.meta.spatial.core.Vector3
import com.meta.spatial.toolkit.Transform
import com.meta.spatial.toolkit.createPanelEntity
override fun onSceneReady() {
super.onSceneReady()
// Existing scene setup code...
scene.setReferenceSpace(ReferenceSpace.LOCAL_FLOOR)
// ... lighting and skybox setup
// Spawn the hello panel at a specific position
Entity.createPanelEntity(
R.id.hello_panel,
Transform(Pose(
Vector3(0f, 0.5f, 1f),
Quaternion(0f, 180f, 0f))),
)
}
Vector3(0f, 0.5f, 1f) positions the panel:
x = 0f: Centered horizontallyy = 0.5f: 0.5 meters above the floorz = 1f: 1 meter in front of the userQuaternion(0f, 180f, 0f): Rotated 180° around Y axis to face the userNote: The Spatial SDK uses a left-handed coordinate system. Positive Z extends forward from the user, positive Y points up, and positive X points right.

Vector3 values. To rotate it, adjust the Quaternion.Entity.createPanelEntity(
R.id.hello_panel,
Transform(Pose(
Vector3(-2f, 1.2f, -1.5f), // X = -2m, Y = +1.2m, Z = -1.5m
Quaternion(0f, 210f, 0f) // 180° to face user + 30° additional rotation
)),
)
| Axis | Positive (+) | Negative (-) | Origin |
|---|---|---|---|
X | +X moves right | -X moves left | X = 0 is scene center |
Y | +Y moves up | -Y moves down | Y = 0 is floor level |
Z | +Z moves forward | -Z moves backward | Z = 0 is user position |
| Use case | Vector3 | Coordinates |
|---|---|---|
Eye level, centered | (0f, 1.5f, 2f) | X=0, +Y = 1.5m, +Z = 2m |
Lower, closer | (0f, 1.0f, 1.5f) | X = 0, +Y = 1m, Z = 1.5m |
Left side | (-1.5f, 1.5f, 1.5f) | X = -1.5m, Y = 1.5m, Z = 1.5m |
Right side | (1.5f, 1.5f, 1.5f) | X = +1.5m, Y = 1.5m, Z = 1.5m |
Above user | (0f, 2.5f, 1f) | X = 0, Y = 2.5m, Z = 1m |
Quaternion constructor with three parameters accepts Euler angles in degrees: Quaternion(pitch, yaw, roll). Pitch rotates around the X axis (tilting up/down), yaw rotates around Y (turning left/right), and roll rotates around Z (tilting sideways).Note: The SDK provides twoQuaternionconstructors: the primary constructorQuaternion(w, x, y, z)accepts quaternion components directly, while the convenience constructorQuaternion(pitch, yaw, roll)accepts Euler angles in degrees. This tutorial uses the Euler angle constructor for readability.

layerConfig and related settings to your panel registration. Update the panel configuration in registerPanels():import com.meta.spatial.compose.composePanel
import com.meta.spatial.runtime.LayerConfig
import com.meta.spatial.toolkit.PanelRegistration
PanelRegistration(R.id.hello_panel) {
config {
width = 1.5f
height = 0.8f
layerConfig = LayerConfig()
enableTransparent = true
themeResourceId = R.style.PanelAppThemeTransparent
}
composePanel {
setContent { HelloPanel() }
}
}
layerConfig = LayerConfig() enables compositor layers for native-resolution rendering.layerConfig, panels render as low-resolution textured meshes in scene space. With compositor layers enabled, panels render at native display resolution, resulting in crisp, readable text.
SpatialTheme for consistent styling across panels. Update HelloPanel.kt to use this theme system:package com.meta.spatial.samples.startersample
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.meta.spatial.uiset.theme.LocalColorScheme
import com.meta.spatial.uiset.theme.SpatialColorScheme
import com.meta.spatial.uiset.theme.SpatialTheme
import com.meta.spatial.uiset.theme.darkSpatialColorScheme
import com.meta.spatial.uiset.theme.lightSpatialColorScheme
@Composable
fun HelloPanel() {
SpatialTheme(colorScheme = getPanelTheme()) {
Column(
modifier = Modifier
.fillMaxSize()
.clip(SpatialTheme.shapes.large)
.background(brush = LocalColorScheme.current.panel)
.padding(48.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Column(
modifier = Modifier.widthIn(max = 400.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "Hello Spatial SDK!",
textAlign = TextAlign.Center,
style = SpatialTheme.typography.headline1Strong.copy(
color = SpatialTheme.colorScheme.primaryAlphaBackground
),
)
}
}
}
}
@Composable
private fun getPanelTheme(): SpatialColorScheme =
if (isSystemInDarkTheme()) darkSpatialColorScheme() else lightSpatialColorScheme()
| Element | Purpose |
|---|---|
SpatialTheme(colorScheme = ...) | Wraps content in Meta’s spatial theme system |
clip(SpatialTheme.shapes.large) | Applies rounded corners matching other panels |
background(brush = LocalColorScheme.current.panel) | Adds the translucent glass-like background |
padding(48.dp) | Consistent inset matching WelcomePanel |
SpatialTheme.typography.headline1Strong | Uses the spatial typography for headings |
primaryAlphaBackground | Semi-transparent text color for glass effect |
widthIn(max = 400.dp) | Constrains text width for readability |
getPanelTheme() | Adapts to system dark/light mode |

ids.xmlPanelRegistration and composePanel {}Entity.createPanelEntity()Transform, Pose, Vector3, and QuaternionSpatialTheme for consistent styling across panels