Skip to content


A Modifier for handling pan & zoom gestures, designed to be shared across all your media composables so that your users can use the same familiar gestures throughout your app. It offers,

  • Pinch to zoom and flings
  • Double tap to zoom
  • Single finger zoom (double tap and hold)
  • Haptic feedback for over/under zoom
  • Compatibility with nested scrolling
  • Click listeners


      brush = Brush.linearGradient(listOf(Color.Cyan, Color.Blue)),
      shape = RoundedCornerShape(4.dp)

While Modifier.zoomable() was primarily written with images & videos in mind, it can be used for anything such as text, canvas drawings, etc.

Edge detection

Without edge detection With edge detection

For preventing your content from over-zooming or over-panning, Modifier.zoomable() will use your content's layout size by default. This is good enough for composables that fill every pixel of their drawing space.

For richer content such as an Image() whose visual size may not always match its layout size, Modifier.zoomable() will need your assistance.

val state = rememberZoomableState()
val painter = resourcePainter(R.drawable.example)

LaunchedEffect(painter.intrinsicSize) {

  modifier = Modifier
  painter = painter,
  contentDescription = ,
  contentScale = ContentScale.Inside,
  alignment = Alignment.Center,

Click listeners

For detecting double taps, Modifier.zoomable() consumes all tap gestures making it incompatible with Modifier.clickable() and Modifier.combinedClickable(). As an alternative, its onClick and onLongClick parameters can be used.

  state = rememberZoomableState(),
  onClick = {  },
  onLongClick = {  },

Applying gesture transformations

When pan & zoom gestures are received, Modifier.zoomable() automatically applies their resulting scale and translation onto your content using Modifier.graphicsLayer().

This can be disabled if your content prefers applying the transformations in a bespoke manner.

val state = rememberZoomableState(
  autoApplyTransformations = false

  modifier = Modifier
  text = "Nicolas Cage",
  style = state.contentTransformation.let {
    val weightMultiplier = if (it.isUnspecified) 1f else it.scale.scaleX
      fontSize = 36.sp,
      fontWeight = FontWeight(400 * weightMultiplier),