skydoves/Balloon
 Watch   
 Star   
 Fork   
2 days ago
Balloon

2.0.1

Balloon 2.0.1 is a patch release on top of the 2.0 Compose Multiplatform rewrite.

Fixed

  • The balloon no longer jumps to the top-left corner when its anchor scrolls off-screen (#1022, #1024). Anchor bounds were captured with boundsInWindow(), which clips: an anchor scrolled out of a Modifier.verticalScroll column collapses to Rect.Zero, so the balloon was placed against the window origin instead of its anchor, and the off-screen dismissal never fired.

    The anchor rect now comes from the anchor's own four corners, so a balloon stays glued to its anchor for the whole scroll. A balloon is dismissed once its anchor is genuinely out of view — including an anchor clipped away by a scrolling container while still inside the window, which was never detected before. An anchor that has not appeared yet does not count, so a balloon shown while its anchor is still animating in waits for it.

Installation

Compose Multiplatform:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("com.github.skydoves:balloon:2.0.1")
        }
    }
}

Android only:

dependencies {
    implementation("com.github.skydoves:balloon:2.0.1")
}

Full Changelog: https://github.com/skydoves/Balloon/compare/2.0.0...2.0.1

17 days ago
Balloon

2.0.0

Balloon 2.0.0 is a full rewrite on Compose Multiplatform. One artifact now runs on Android, iOS, Desktop and Web, and the API is composables and state instead of Context, View and XML.

If you need the View implementation, it stays available at 1.7.6 and is documented separately. Nothing about 1.x is going away.

Supported platforms

Target Artifact
Android balloon-android
Desktop (JVM) balloon-desktop
iOS (arm64) balloon-iosarm64
iOS (simulator, arm64) balloon-iossimulatorarm64
iOS (x64) balloon-iosx64
Web (Wasm) balloon-wasm-js

Gradle picks the right one. Depend on com.github.skydoves:balloon and nothing else.

Installation

Compose Multiplatform:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("com.github.skydoves:balloon:2.0.0")
        }
    }
}

Android only:

dependencies {
    implementation("com.github.skydoves:balloon:2.0.0")
}

balloon-compose is gone. It is folded into balloon.

Quick start

val style = rememberBalloonBuilder {
    setArrowSize(10.dp)
    setPadding(12.dp)
    setCornerRadius(8.dp)
    setBackgroundColor(Color(0xFF785EF0))
    setBalloonAnimation(BalloonAnimation.ELASTIC)
}
val balloonState = rememberBalloonState(style)

Balloon(
    state = balloonState,
    balloonContent = { Text(text = "Now you can edit your profile!", color = Color.White) },
) {
    Button(onClick = { balloonState.showAlignTop() }) {
        Text(text = "Edit profile")
    }
}

Wrap your screen in BalloonHost { ... } once, at the root, and every balloon inside it works.

What changed from 1.7.6

1.7.6 2.0.0
Artifacts balloon, balloon-compose balloon
Platforms Android Android, iOS, Desktop (JVM), Web (Wasm)
Rendering PopupWindow plus Android views Compose Popup and a Compose Shape
Package com.skydoves.balloon, com.skydoves.balloon.compose com.skydoves.balloon
Content setText, TextForm, IconForm, setLayout a @Composable slot
Anchoring you pass a View to every show call BalloonState knows its own anchor
Lifecycle setLifecycleOwner, manual disposal composition disposal

There is no Context, no View, no Drawable, no Typeface and no XML anywhere in the API.

What is new

  • Two ways to attach a balloon. Wrap an anchor with Balloon(...), or decorate one in place with Modifier.balloon(...).
  • A composable body. The balloon content is a slot, so you build it like any other UI. Interactive children work: a tap a child consumes never reaches the balloon's own handler.
  • Coroutine friendly. Every show has a suspend twin. awaitAlignBottom() returns when the balloon closes, so a sequence of tips reads as straight line code.
  • Restyle in place. rememberBalloonState re-applies the style on every recomposition, so an animated style updates a balloon that is already showing without hiding it.
  • derive. base.derive { setBackgroundColor(Color.Red) } makes a variant of an existing style using the same builder block.
  • Placement that flips. When the requested side has no room and the opposite side has some, the balloon moves and the arrow follows it.
  • A baseline profile ships in the Android artifact.

Every 1.x builder setter that has a multiplatform meaning is present, with the same name and the same defaults, so most builder blocks port across unchanged.

Deliberate differences

These behave differently from 1.7.6 on purpose. Each was found by rendering both implementations and diffing the result pixel by pixel.

  1. A hidden arrow takes no space. setIsVisibleArrow(false) puts the body flush against the anchor. 1.x left an arrowHeight - 1px gap.
  2. The arrow points at the anchor by default. In 1.x arrowOrientation defaulted to BOTTOM, so showAlignStart() on a default builder left the arrow pointing down.
  3. Balloons flip instead of clamping. 1.x only flipped vertically, and horizontally it slid the balloon along the window edge until it overlapped its own anchor.
  4. setBalloonStroke draws the thickness you asked for, around the arrow too.
  5. No drop shadow. elevation reserves its space and drives the width math, but the shadow is not drawn: Compose can only cast a shadow from a convex outline, and a balloon with an arrow notch is not convex. Use Modifier.shadow(...) inside the slot if you need one.
  6. BalloonOverlayShape.Circle and RoundRect take Dp, where 1.x took raw pixels. Convert deliberately.
  7. The arrow never enters a rounded corner. Its base is clamped cornerRadius + arrowWidth / 2 in from each end.
  8. setAutoDismissDuration(0L) means "never". 1.x used -1L as the disabled sentinel and treated 0L as "dismiss immediately".
  9. BalloonHighlightAnimation.ROTATE animates out of the box. In 1.x it did nothing without an explicit setBalloonRotationAnimation.

Everything else matches to the pixel.

Not carried over

Mostly Android only surface with no multiplatform equivalent:

  • setText, TextForm, IconForm, setLayout, and everything taking a resource id. Use the composable slot.
  • setOnBalloonTouchListener and setOnBalloonOutsideTouchListener. MotionEvent is Android only. Use Modifier.pointerInput inside the slot.
  • setPreferenceName, setShowCounts, runIfReachedShowCounts. No multiplatform key value store is assumed. Gate show() with your own storage.
  • setLifecycleOwner, setDismissWhenLifecycleOnPause, setLifecycleObserver. Composition disposal dismisses the balloon.
  • setIsStatusBarVisible, setIsAttachedInDecor, setIsClippingEnabled, setRtlSupports. PopupWindow specific, or handled by LocalLayoutDirection.

Listeners moved from the builder onto the state: balloonState.onBalloonClick, onDismiss and onOverlayClick.

Migrating

The migration guide maps every 1.x setter to its 2.0.0 counterpart, including the ones that were dropped and what to use instead.

Documentation

Staying on 1.7.6

The View implementation is unchanged and still published. Keep using it with:

implementation("com.github.skydoves:balloon:1.7.6")
implementation("com.github.skydoves:balloon-compose:1.7.6")

2.0.0 does not replace it in place. It is a different API under the same coordinates, so pin the version you want.

2026-04-16 14:16:19
Balloon

1.7.6

What's Changed

New Contributors

Full Changelog: https://github.com/skydoves/Balloon/compare/1.7.5...1.7.6

2026-03-10 19:26:33
Balloon

1.7.5

What's Changed

Full Changelog: https://github.com/skydoves/Balloon/compare/1.7.4...1.7.5

2026-03-03 08:05:30
Balloon

1.7.4

What's Changed

Full Changelog: https://github.com/skydoves/Balloon/compare/1.7.3...1.7.4

2026-01-10 07:34:20
Balloon

1.7.3

What's Changed

Full Changelog: https://github.com/skydoves/Balloon/compare/1.7.2...1.7.3

2026-01-04 14:22:32
Balloon

1.7.2

What's Changed

Full Changelog: https://github.com/skydoves/Balloon/compare/1.7.1...1.7.2

2025-12-28 10:55:42
Balloon

1.7.1

What's Changed

New Contributors

Full Changelog: https://github.com/skydoves/Balloon/compare/1.7.0...1.7.1

2025-12-21 14:13:41
Balloon

1.7.0

Release Notes for Balloon 1.7.0.

New Features

  • Introduce Modifier.balloon() and BalloonState for Jetpack Compose (#904)
    • New modifier-based API for attaching balloon tooltips to composables
    • rememberBalloonState() for managing balloon state
    • Deprecates the Balloon() composable and BalloonWindow interface
  • Support individual properties for BalloonOverlayRoundRect (#903)
    • Allows setting individual corner radii for overlay round rectangles
  • Implement effective arrow sizes (#900)
    • Better arrow size calculations for improved positioning

Now, the Balloon composable function will be deprecated. Migration guides from Balloon composable to Modifier.balloon():

Before (Deprecated)

  var balloonWindow: BalloonWindow? by remember { mutableStateOf(null) }

  Balloon(
    builder = builder,
    onBalloonWindowInitialized = { balloonWindow = it },
    onComposedAnchor = { balloonWindow?.showAlignTop() },
    balloonContent = {
      Text(text = "Tooltip content")
    },
  ) {
    Button(onClick = { balloonWindow?.showAlignTop() }) {
      Text(text = "Show Balloon")
    }
  }

After (New API)

  val balloonState = rememberBalloonState(builder)

  LaunchedEffect(Unit) {
    balloonState.showAlignTop()
  }

  Button(
    modifier = Modifier.balloon(balloonState) {
      Text(text = "Tooltip content")
    },
    onClick = { balloonState.showAlignTop() },
  ) {
    Text(text = "Show Balloon")
  }

Bug Fixes and Improvements

  • Disable focus to prevent focus highlight on D-pad navigation (#902)
  • Apply stroke/border in non-clipped mode as well (#901)
  • Migrate to Subcompose layout to measure layout sizes (#898)
    • Fixes black screen issue during SplashScreen (#786)
  • Correct measuring logic with adjusted padding sequences (#897)
  • Use EXACTLY mode for fixed heights, AT_MOST for wrap content (#896)
    • Fixes nested layout measurement issues
  • Fix: Clear any theme-applied background tint (#893)

Deprecations

The following APIs are now deprecated in favor of the new Modifier.balloon() API:

  • Balloon() composable - Use Modifier.balloon() with rememberBalloonState()
  • BalloonWindow interface - Use BalloonState
  • rememberBalloonWindow() - Use rememberBalloonState()
  • awaitBalloonWindows() and AwaitBalloonWindowsDsl
2025-08-10 15:28:43
Balloon

1.6.13

What's Changed

New Contributors

Full Changelog: https://github.com/skydoves/Balloon/compare/1.6.12...1.6.13