Overlay Compose UI
MaplibreMap draws Compose UI on top of the map in its trailing
overlay block. Without a block, it draws MapOverlay.Default: a
scale bar and a compass along the top edge, and the MapLibre logo and an
attribution button along the bottom edge. The scale bar appears for a moment
when the zoom changes. The compass appears while the map is rotated or tilted.
MaplibreMap()The attribution button shows the attribution text of the sources in the style.
Choose a preset
Section titled “Choose a preset”include draws a preset inside the overlay block.
MapOverlay.Full adds zoom buttons to the default controls, which
helps users with a mouse. MapOverlay.AttributionOnly keeps only
the logo and the attribution button.
MaplibreMap { include(MapOverlay.Full) }An empty block draws nothing. Use it when your app shows the attribution
somewhere else. Call attributions on mapState.style to get the
attribution text of the style’s sources, and draw it with
AttributionLinks.
MaplibreMap {}Add your own controls
Section titled “Add your own controls”The overlay block lays out its children like a Box that covers the map. This
example puts the controls in a Box with safeDrawingPadding(), so they stay
clear of the system bars, and places each one with Modifier.align.
MaplibreMap { Box(Modifier.fillMaxSize().safeDrawingPadding().padding(8.dp)) { MaplibreLogo(Modifier.align(Alignment.BottomStart)) ExpandingAttributionButton( modifier = Modifier.align(Alignment.TopEnd), contentAlignment = Alignment.TopEnd, ) }}The library’s controls, such as
ZoomButtons and
CompassButton, are composables you can
place the same way.
Use Material 3 colors
Section titled “Use Material 3 colors”The base controls use fixed colors that are readable on light and dark maps.
The maplibre-compose-material3 module draws the same controls with the colors
and typography of your Material 3 theme. Add the dependency:
[libraries]maplibre-composeMaterial3 = { module = "org.maplibre.compose:maplibre-compose-material3", version = "0.20.0" }commonMain.dependencies { implementation(libs.maplibre.composeMaterial3)}Then include MapOverlay.Material3. It places
the same controls as MapOverlay.Default.
MapOverlay.Material3Full and MapOverlay.Material3AttributionOnly are the
Material 3 versions of MapOverlay.Full and MapOverlay.AttributionOnly.
MaplibreMap { include(MapOverlay.Material3) }The module has a Material 3 version of each control except the MapLibre logo,
in the org.maplibre.compose.material3 package. Use them in your own layout:
MaplibreMap { Box(Modifier.fillMaxSize().safeDrawingPadding().padding(8.dp)) { CompassButton(Modifier.align(Alignment.TopEnd)) ZoomButtons(Modifier.align(Alignment.CenterEnd)) MaplibreLogo(Modifier.align(Alignment.BottomStart)) ExpandingAttributionButton(Modifier.align(Alignment.BottomEnd)) }}Read the map in a control
Section titled “Read the map in a control”A control can read the map it is drawn on through
LocalMapState. You do not need to pass the map state to it. This
label shows the current zoom level and updates as the user zooms:
@Composablefun ZoomLabel(modifier: Modifier = Modifier) { val mapState = checkNotNull(LocalMapState.current) { "ZoomLabel must be inside a map overlay" } Text("Zoom ${mapState.cameraPosition.zoom.roundToInt()}", modifier)}
@Composablefun MapWithZoomLabel() { MaplibreMap { include(MapOverlay.Default) ZoomLabel(Modifier.align(Alignment.TopCenter).safeDrawingPadding().padding(8.dp)) }}LocalMapState is null outside a map. LocalViewport
gives the camera, size, and visible area of the last frame the map drew.
Keep controls clear of your UI
Section titled “Keep controls clear of your UI”When your app draws UI over part of the map, such as a bottom panel, pass that
area to viewportInsets. The default controls then stay above the panel, and
camera moves center their target in the part of the map the user can see.
val panelHeight = 160.dpBox(Modifier.fillMaxSize()) { MaplibreMap(viewportInsets = PaddingValues(bottom = panelHeight)) Surface(Modifier.align(Alignment.BottomCenter).fillMaxWidth().height(panelHeight)) { Text("Ferry schedule", Modifier.padding(16.dp)) }}Your own controls can read the same insets from
LocalViewportInsets and apply them as padding.
Pin UI to a location
Section titled “Pin UI to a location”MapOverlayScope.placedAt puts a composable on a geographic
position. The composable moves with the map when the user pans or zooms. Use it
for a label or a button that belongs to a place. Apply it to a direct child of
the overlay block.
MaplibreMap { include(MapOverlay.Default) Surface( modifier = Modifier.placedAt(position, alignment = Alignment.BottomCenter).padding(bottom = 8.dp), shape = RoundedCornerShape(8.dp), ) { Text("Next sailing 12:40", Modifier.padding(8.dp)) }}The alignment chooses which point of the composable sits on the position.
Alignment.BottomCenter puts the bottom of the label on the position, and the
bottom padding lifts it a little above.
Point to an off-screen location
Section titled “Point to an off-screen location”PointerPinButton shows a pin at the
edge of the map that points toward a position outside the view. The pin moves
along an oval that fills its layout, and it hides while the position is inside
that oval. In this example, a tap moves the camera to the position.
val mapState = rememberMapState()val scope = rememberCoroutineScope()MaplibreMap(state = mapState) { include(MapOverlay.Default) GeographicLayout(Modifier.safeDrawingPadding().padding(8.dp)) { PointerPinButton( targetPosition = position, onClick = { scope.launch { mapState.animateCamera(CameraUpdate(center = position)) } }, ) { Icon( painterResource(Res.drawable.filter_center_focus_24px), contentDescription = "Show the ferry terminal", ) } }}GeographicLayout sets the area that the pin moves around in. Here
the padding keeps the pin away from the system bars and the edge of the map.
To draw your own indicator, use MapOverlayScope.placedTowards with
rememberPlacedTowardsState. The state’s
angleDegrees tells you
which way to rotate it.