Library / SDK
skydoves/Balloon avatar
skydoves/Balloon

Balloon 2 deleted Context, View and XML from its API, and its arrow points at a fraction of two different things

:balloon: Modernized and sophisticated tooltips, fully customizable with an arrow and animations for Compose and Kotlin Multiplatform.

4,018 stars310 forksKotlinApache-2.0

At a glance

What is it?
skydoves/Balloon is an Apache-2.0 tooltip and popup library for Compose Multiplatform whose version 2 is a rewrite that draws everything with Compose instead of a platform popup window, on one artifact covering Android, iOS, desktop and WebAssembly. A balloon is a style plus a state, the arrow position is read as a fraction of the balloon or of the anchor depending on which rule you pick, and every show call has a suspending twin.
Who is it for?
Balloon fits a Compose Multiplatform project that needs a teaching tooltip, a coach mark or a guided prompt on more than one platform and does not want to write the popup twice. It does not fit an existing View-based Android codebase that wants to add a tooltip without a migration, because version 2 is a rewrite rather than an increment, and the old implementation stops at 1.7.6.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Kotlin, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • skydoves/Balloon4,018 ★ · +10
Star history
Repository2026-09-242026-11-14
skydoves/Balloon4k4k

10 Stars in the last 51 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Version 2 removed Context, View and XML from the public API

The 2.0 release is described as a full rewrite rather than an increment, and the sentence that explains why is about what is no longer there. Everything is drawn by Compose instead of using the platform popup window, and the API contains no Context, no View and no XML anywhere. One artifact now covers Android, iOS, desktop on the JVM, and the web through WebAssembly, which is the practical payoff of the rewrite: the same code path on four runtimes instead of a Compose implementation for one and something else for the rest. For anyone arriving from 1.x there is a migration guide, and the old View-based implementation is not deleted but frozen at version 1.7.6 with its own documentation, which is the kind of courtesy that costs the maintainers a branch to keep and saves every stalled user a rewrite.

A balloon is a style and a state, and that split is the whole API

The design principle is stated in one sentence: a balloon is made of two things, a style that describes how it looks, and a state that decides when it shows. They are built separately and then paired. The style comes from a remembered builder, where you set the arrow geometry, the width ratio, the padding, the corner radius, a background colour and the animation, with the animation chosen from a set that includes an elastic option. The state is created from that style, and the state object is what your UI actually holds. Nothing about position, visibility or dismissal lives on the style, so the same style can be reused by several states, and the same state can be rebuilt with a different style when the theme changes without you re-implementing the show and dismiss logic.

Modifier.balloon refuses to fail quietly when the host is missing

There are two ways to attach a balloon, and the second one is the one with a rule attached. The first is a composable that wraps your anchor, with the balloon body supplied as content and the anchor placed in the trailing lambda. The second is a modifier you apply to an existing composable instead of wrapping it, which is convenient when you do not control the layout. That modifier requires a host composable to exist somewhere above it, and that host is what actually renders the popup and the overlay scrim. You wrap your screen in it once and every use below it works. What happens if you forget is the part worth remembering: it throws an exception that says exactly what is missing, rather than rendering nothing. A tooltip library that fails silently is a tooltip nobody debugs.

The arrow position is a fraction of the balloon or of the anchor

The arrow is the feature this library exists for, and its positioning has two modes that are easy to confuse. The position is a number between zero and one, but what that number is a fraction of depends on the rule you choose. Under the balloon rule it is a fraction of the balloon, which means the arrow stays at the same spot on the card however the card moves. Under the anchor rule it is a fraction of the anchor, which means the arrow keeps pointing at the same spot on the button no matter where the balloon lands. The documentation is explicit about the reason for the second mode: it survives placement changes. That mode also adds a gap, computed from the arrow size multiplied by a padding ratio plus a fixed padding, so the arrow does not touch the ends of the balloon when it is pinned near one.

The balloon flips when the requested side has no room

Three behaviours stack to keep a balloon usable, and none of them require you to measure anything. First, the arrow edge is derived from the alignment you asked to show with, so it points back at the anchor without you naming an arrow edge at all. Second, when the requested side has no space and the opposite side does, the balloon flips over and the arrow follows it, so the alignment call expresses a preference rather than a fixed outcome. Third, a final clamp keeps the balloon inside the window. If you need the arrow pinned to one edge regardless of where the balloon ends up, there is a separate orientation setting and a rule that fixes it. The size settings behave the same way: width and height describe the whole popup box, which is the visible card plus its margins plus the elevation inset, so setting the elevation to zero and the margin to nothing is what makes a size match the card you see.

Every show call has a suspending twin, which is how sequences get written

Visibility is controlled entirely from the state object, and the naming is deliberate. You show above the anchor, below it, at the leading or trailing side, as a drop-down aligned to the leading edges, at the centre with a given alignment, or at an explicit alignment with pixel offsets. Then there is a toggle, a dismiss, and an update that moves an already visible balloon to a new alignment without replaying the animation, which is the call you want when the layout changes underneath it. A dismiss-with-delay takes a scope and a duration. Visibility itself is observable, so a Compose recomposition can react to it. The feature that changes how you write this, though, is that each of those methods has a suspending counterpart which returns once the balloon has been dismissed, so a sequence of coach marks reads as a sequence of awaited calls rather than as callbacks and state flags.

One artifact, six targets, and four sample apps

The Gradle setup has two shapes for one dependency, and the difference is only where the coordinate goes. In a Compose Multiplatform project it is added to the common source set; in an Android-only project it is added as an ordinary dependency:

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

Both name the same published version. The supported targets are listed explicitly, and the list is worth reading because it is not simply the platforms: Android, the JVM for desktop, two iOS architectures plus the simulator one, and WebAssembly for the web. The repository mirrors those targets with a separate sample application for Android, desktop, iOS and the web, a shared sample module underneath them, and a benchmark module, which is an unusual thing to keep in a tooltip library's tree and tells you the author cared about render cost. Documentation is built with a static site generator, formatting is enforced by a dedicated module, and dependency updates are automated.

Editorial conclusion

Balloon fits a Compose Multiplatform project that needs a teaching tooltip, a coach mark or a guided prompt on more than one platform and does not want to write the popup twice. It does not fit an existing View-based Android codebase that wants to add a tooltip without a migration, because version 2 is a rewrite rather than an increment, and the old implementation stops at 1.7.6. Two things to check before you plan the upgrade. Whether your UI is already Compose, since the version 2 API has no Context, no View and no XML and the migration guide exists precisely because the 1.x entry points do not map onto it. And how much of your layout relies on the popup's outer box rather than its card, because width and height size the whole box including margins and the elevation inset, so setting a width and expecting the visible card to be that size will be wrong by a fixed amount.

Frequently asked questions

What is skydoves Balloon?

It is a tooltip and popup library for Compose Multiplatform, licensed Apache-2.0 and written in Kotlin. A balloon is composed of a style that describes its appearance and a state that decides when it shows, and it can be attached either by wrapping your anchor in a composable or by applying a modifier to an existing composable. Version 2 is a rewrite that draws with Compose instead of a platform popup window and contains no Context, View or XML.

How do I add Balloon to a Compose Multiplatform project?

Add the dependency to your module's Gradle build file. In a Compose Multiplatform project it goes in the common source set's dependencies block, and in an Android-only project it goes in a normal dependencies block. The published targets cover Android, the JVM for desktop, iOS on device and simulator architectures, and WebAssembly for the web.

What is the difference between Balloon 2.0 and 1.x?

Version 2 is a full rewrite on Compose Multiplatform where one artifact runs on Android, iOS, desktop and the web and everything is drawn by Compose rather than a platform popup window, with no Context, View or XML in the API. A migration guide is included for people coming from 1.x, and the View-based implementation is still available at version 1.7.6 with separate documentation.

Why does my Modifier.balloon crash?

The modifier needs a host composable somewhere above it, and that host is what renders the popup and the overlay scrim. Wrap your screen in it once and every use below it works. Forgetting it throws an exception naming the missing host instead of rendering nothing, which the documentation calls out as deliberate behaviour.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. skydoves/Balloon on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/skydoves-balloon.svg)](https://hysenlabs.com/projects/skydoves-balloon)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/skydoves-balloon.svg)](https://hysenlabs.com/projects/skydoves-balloon)

Was this useful?