Overlay Compose UI
MaplibreMap draws controls on top of the map. The overlay
block replaces the default controls when you supply it. The default draws a
scale bar and a compass along the top edge. It draws the MapLibre logo and an
attribution button along the bottom edge. The scale bar and the compass appear
only while they are relevant.
MaplibreMap()Two variants cover common needs. MapOverlay.Full adds zoom in and zoom out
buttons at the middle of the end edge. MapOverlay.AttributionOnly draws only
the logo and the attribution button.
Replace the overlay
Section titled “Replace the overlay”An empty block, or MapOverlay.None, draws no controls. Use it when your app
shows the attribution somewhere else, such as an about screen.
MaplibreMap {}The trailing block draws the controls that you list. Inside a Box, Modifier.align positions
a control against an inset edge of the map.
MaplibreMap { Box(Modifier.fillMaxSize().safeDrawingPadding().padding(8.dp)) { MaplibreLogo(Modifier.align(Alignment.BottomStart)) ExpandingAttributionButton( modifier = Modifier.align(Alignment.TopEnd), contentAlignment = Alignment.TopEnd, ) }}Theme the controls with Material 3
Section titled “Theme the controls with Material 3”The base library draws controls with a fixed palette that stays legible on any
basemap. The maplibre-compose-material3 module draws the same controls with
your Material 3 color scheme and typography. Add the dependency:
[libraries]maplibre-composeMaterial3 = { module = "org.maplibre.compose:maplibre-compose-material3", version = "0.19.0" }commonMain.dependencies { implementation(libs.maplibre.composeMaterial3)}MapOverlay.Material3 draws the same controls
as MapOverlay.Default in the same places. Include it in the block:
MaplibreMap { include(MapOverlay.Material3) }MapOverlay.Material3AttributionOnly and MapOverlay.Material3Full apply the
theme to the other presets.
Each Material 3 control is also a composable that you can place in the trailing block:
MaplibreMap { Box(Modifier.fillMaxSize().safeDrawingPadding().padding(8.dp)) { val mapState = checkNotNull(LocalMapState.current) ScaleBar( metersPerDp = { mapState.viewport?.metersPerDpAtTarget ?: 0.0 }, // (1)! modifier = Modifier.align(Alignment.TopStart), ) // (2)! CompassButton(modifier = Modifier.align(Alignment.TopEnd)) ZoomButtons(Modifier.align(Alignment.CenterEnd)) MaplibreLogo(Modifier.align(Alignment.BottomStart)) ExpandingAttributionButton( modifier = Modifier.align(Alignment.BottomEnd), contentAlignment = Alignment.BottomEnd, ) }}ScaleBarcallsmetersPerDpwhile it draws, so read the map state inside the lambda rather than passing a value.ScaleBarandCompassButtonstay on screen. TheDisappearingversions thatMapOverlay.Material3uses fade in when the zoom or the orientation changes, and fade out once it settles.
Map context inside overlays
Section titled “Map context inside overlays”Overlay content uses MapOverlayScope to position direct children.
Controls are ordinary composables that read the enclosing map through
LocalMapState. LocalViewport exposes its current viewport,
and LocalViewportInsets exposes its viewport insets. These values
remain available inside nested composables and layouts. A nested map provides its own context.
Inset overlays and the camera
Section titled “Inset overlays and the camera”The overlay fills the map without implicit padding. Arrange custom controls with
Compose layouts such as Box, Row, and Column, and apply padding and system
insets to those layouts.
The built-in presets use the larger of viewportInsets and safe-drawing insets on
each edge, then add MapOverlay.Spacing. They consume padding and insets
using Compose layout rules, accounting for safe areas handled by an outer layout.
The padding takes effect without waiting for a map frame.
viewportInsets also shifts the camera center. For custom controls that follow this
padding, read LocalViewportInsets and apply it to their container.
val mapInsets = WindowInsets.safeDrawing.union(WindowInsets(bottom = 128.dp)) // (1)!MaplibreMap(viewportInsets = mapInsets.asPaddingValues())uniontakes the larger inset on each edge.
Camera padding frames the target inside these insets without moving controls. Padding on a bounding-box camera move adds a margin inside both for that fit.
Pin Compose UI to a location
Section titled “Pin Compose UI to a location”MapOverlayScope.placedAt puts a child on a geographic position.
Include MapOverlay.Default to keep the default controls. A pan, a
zoom, or a window resize moves the child with the map.
MaplibreMap { include(MapOverlay.Default) Text( "Next sailing 12:40", Modifier.placedAt(position, alignment = Alignment.BottomCenter) .padding(bottom = 8.dp), // (1)! )}Alignment.BottomCentersits the bottom edge of the chip on the point. Padding lifts the chip above the feature.
Point at an off-screen location
Section titled “Point at an off-screen location”Use MapOverlayScope.placedTowards for a direction indicator when
a location is out of view. It places the child along the parent layout’s inscribed
ellipse and hides it while the location is inside that ellipse.
Use GeographicLayout to choose a smaller placement region. Apply sizing
and padding to that layout; geographic positions still refer to the enclosing map,
including when the layout moves. Placement modifiers apply to direct children of
the map overlay or GeographicLayout. To place a compound marker, apply the modifier
to its outer container. Sizing and padding on a child describe that child, independent
of the placement modifier’s position in the chain.
MaplibreMap { include(MapOverlay.Default) GeographicLayout(Modifier.safeDrawingPadding().padding(8.dp)) { val placement = rememberPlacedTowardsState() // (1)! Text( "▲", Modifier.placedTowards(position, state = placement).graphicsLayer { rotationZ = placement.angleDegrees // (2)! }, ) }}- The state stores the placement that the overlay computed.
angleDegreesis the direction of the target, clockwise from screen-up.
PointerPinButton uses this modifier
to display a pin-shaped button that points at the target. The Material 3 module
provides a themed version of it.