With Jetpack Compose for XR, you can declaratively build your spatial UI and layout using familiar Compose concepts such as rows and columns. This lets you extend your existing Android UI into 3D space or build entirely new immersive 3D applications.
If you are spatializing an existing Android Views-based app, you have several development options. You can use interoperability APIs, use Compose and Views together, or work directly with the SceneCore library. See our guide to working with views for more details.
About subspaces and spatialized components
When you're writing your app for Android XR, it's important to understand the concepts of subspace and spatialized components.
About subspace
When developing for Android XR, you'll need to add a subspace to your app or layout. A subspace is a partition of 3D space within your app where you can place 3D content, build 3D layouts, and add depth to otherwise 2D content. A subspace is rendered only when spatialization is enabled. In Home Space or on non-XR devices, any code within that subspace is ignored.
There are a few ways to create a subspace:
Subspace: This composable creates a new, independent Spatial UI hierarchy. It does not inherit the spatial position, orientation, or scale of any parentSubspaceit is nested within.Subspaceis automatically bound by the system's recommended content box.PlanarEmbeddedSubspace: This composable can be placed within your app's UI hierarchy, allowing you to maintain layouts for 2D and spatial UI.PlanarEmbeddedSubspacerespects the constraints and positioning of its parent. The 3D content placed inside it is then positioned relative to this 2D-defined area.
For more information, see Add a subspace to your app.
About spatialized components
Subspace composables: These components can only be rendered in a subspace.
They must be enclosed within Subspace before being placed within a 2D layout.
A SubspaceModifier lets you add attributes like depth, offset, and
positioning to your subspace composables.
Other spatialized components don't require being called inside a subspace. They consist of conventional 2D elements wrapped within a spatial container. These elements can be used within 2D or 3D layouts if defined for both. When spatialization is not enabled, their spatialized features will be ignored and they will fall back to their 2D counterparts.
Create a spatial panel
A SpatialPanel is a subspace composable that lets you display app
content–for example, you could display video playback, still images, or any
other content in a spatial panel.

You can use SubspaceModifier to change the size, behavior, and positioning of
the spatial panel, as shown in the following example.
Subspace { SpatialPanel( SubspaceModifier .height(824.dp) .width(1400.dp) .movable() .resizable(), ) { SpatialPanelContent() } }
@Composable fun SpatialPanelContent() { Box( Modifier .background(color = Color.Black) .height(500.dp) .width(500.dp), contentAlignment = Alignment.Center ) { Text( text = "Spatial Panel", color = Color.White, fontSize = 25.sp ) } }
Key points about the code
- Because
SpatialPanelAPIs are subspace composables, you must call them insideSubspace. Calling them outside of a subspace throws an exception. - The size of the
SpatialPanelhas been set using theheightandwidthspecifications on theSubspaceModifier. Omitting these specifications lets the size of the panel be determined by the measurements of its contents. - Allow the user to move a panel by adding a
movablesubspace modifier. - Allow the user to resize a panel by adding a
resizablesubspace modifier. - See our spatial panel design guidance for details on sizing and positioning. See our reference documentation for more specifics on code implementation.
How the movable modifier works
As a user moves a panel away from them, by default, the movable modifier
scales the panel in a similar way to how panels are resized by the system in
home space. All children content inherit this behavior. To disable this,
set the shouldScaleWithDistance parameter to false.
Create an orbiter
An orbiter is a spatial UI component. It's designed to be attached to a
corresponding spatial panel or spatial layout component like SpatialColumn,
SpatialRow, or SpatialBox. An orbiter typically contains navigation and
contextual action items related to the entity it's anchored to. For example,
if you've created a spatial panel to display video content, you could add
video playback controls inside an orbiter.

As shown in the following example, call an orbiter inside the 2D layout in a
SpatialPanel to wrap user controls like navigation. Doing so extracts them
from your 2D layout and attaches them to the spatial panel according to your
configuration.
Subspace { SpatialPanel( SubspaceModifier .height(824.dp) .width(1400.dp) .resizable() .movable(), ) { SpatialPanelContent() OrbiterExample() } }
@Composable fun OrbiterExample() { Orbiter( alignment = OrbiterAlignment.BottomCenter( edgeOffsetType = OrbiterEdgeOffsetType.OuterEdge, offset = DpVolumeOffset(y = 96.dp) ), ) { Surface(Modifier.clip(CircleShape)) { Row( Modifier .background(color = Color.Black) .height(100.dp) .width(600.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { Text( text = "Orbiter", color = Color.White, fontSize = 50.sp ) } } } }
Key points about the code
- Because orbiters are spatial UI components, the code can be reused in 2D or 3D layouts. In a 2D layout, your app renders only the content inside the orbiter and ignores the orbiter itself.
- Check out our design guidance for more information on how to use and design orbiters.
Add multiple spatial panels to a spatial layout
You can create multiple spatial panels and place them within a spatial layout
using SpatialRow, SpatialColumn, SpatialBox, and
SpatialSpacer.

The following code example shows how to do this.
Subspace { SpatialRow { SpatialColumn { SpatialPanel(SubspaceModifier.height(250.dp).width(400.