In Jetpack Compose Glimmer, the surface component is a fundamental
building block that represents a distinct visual area or a physical boundary for
components such as buttons and cards.
A surface is responsible for the following visual and physical properties:
- Clipping: Clips its children to a specified shape.
- Border: Draws an inner border to emphasize the component boundary. When focused, it draws a wider border with a focused highlight.
- Background: Applies a background color to the surface area.
- Depth effects: Renders
DepthEffectshadows based on the component's state (such as default versus focused). - Content Color: Provides a color for text and icons inside the surface, calculated by default from the background color.
- Interaction States: Draws a pressed overlay when the surface is pressed and a wider border with a highlight when focused.
Example: Create a surface
The following code creates a surface with clipping, a background, and default borders:
@Composable fun SurfaceSample() { Box(Modifier.surface().padding(horizontal = 24.dp, vertical = 20.dp)) { Text("This is a surface") } }
Interaction and Focus
Surfaces aren't focusable by default, so users can't interact with them. In most
cases, surfaces should be interactive to let users consistently move focus and
navigate between components. You can use the Compose focusable modifer
for surfaces that are only intended to be focusable, or the Compose
clickable modifer and other modifiers for surfaces that require actions.
You can create a focusable surface by combining a surface modifier with the
focusable modifier:
@Composable fun FocusableSurfaceSample() { val interactionSource = remember { MutableInteractionSource() } Box( Modifier.surface( // Provide the same interaction source here and to focusable to make sure that // surface appears focused when interacted with. interactionSource = interactionSource ) .focusable(interactionSource = interactionSource) .padding(horizontal = 24.dp, vertical = 20.dp) ) { Text("This is a focusable surface") } }
Key points about the code
- Shared interaction source: Both .
surface()and .focusable()must share the sameinteractionSource. This lets the surface react to focus changes.
Similarly, you can create a clickable surface:
@Composable fun ClickableSurfaceSample() { val interactionSource = remember { MutableInteractionSource() } Box( Modifier.surface( // Provide the same interaction source here and to clickable to make sure that // surface appears focused and pressed when interacted with interactionSource = interactionSource ) .clickable(interactionSource = interactionSource, onClick = {}) .padding(horizontal = 24.dp, vertical = 20.dp) ) { Text("This is a clickable surface") } }
Key points about the code
Shared interaction source: Both .
surface()and .clickable()must share the sameinteractionSource. This ensures that visual states (like press or focus) are synchronized, letting surface react visually to user input.Modifier ordering: The sequence of modifiers is critical. Because .
surface()clips a layout, placing it before .clickable()ensures the touch target is constrained to the surface's shape. If .clickable()comes first, the interaction area might extend beyond the visible, clipped boundaries of the component.
SurfaceDepthEffect
The SurfaceDepthEffect class manages the transition of shadows between
interaction states:
depthEffect: The shadow effect used when the surface is in its default state.focusedDepthEffect: The shadow effect used when the surface is focused.