Library / SDK
FletchMcKee/liquid avatar
FletchMcKee/liquid

Liquid, sampling the pixels behind a Compose node

Liquid RuntimeShader effects for Compose Multiplatform

577 stars25 forksKotlinApache-2.0

At a glance

What is it?
Liquid is a Compose Multiplatform library that lets a modifier node read and distort the content behind it, using RuntimeShaders and ModifierNodeElement, which is what makes frosted glass and lens effects possible inside a Compose UI. It is a young library under Apache-2.0 with a broad platform surface.
Who is it for?
Liquid suits a Compose Multiplatform team that needs glass, lens or distortion effects and has accepted that the effect layer reads from a snapshot of what is behind it rather than from a live texture. It does not suit a project that needs to compose those effects into a single pass with the rest of the frame, because the design separates the content layer from the effect layer.
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 11 days 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 October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A modifier that can see behind itself

Liquid is described as adding a capability to Compose Multiplatform that was not previously available, letting modifier nodes sample and manipulate the pixels behind them. The mechanism is that you tag nodes as `liquefiable`, and that tagging is what allows you to create effects that distort the underlying content in a way that is not normally possible with Compose's rendering pipeline.

That distinction is the whole thing. Compose draws with a retained-ish layer model where each node draws over the last, and there is no way for a node to read what has already been drawn. A frosted glass panel, which needs to blur and sample what is behind it, is not expressible in that model.

The library enables the Liquid Glass style effects, dynamic frosted glass, lenses and other distortions, and the README links to Apple's own documentation on applying Liquid Glass to custom views, which is a hint about the target aesthetic. The implementation is powered by RuntimeShaders, or RuntimeEffects, and by the ModifierNodeElement APIs, and it delivers GPU-accelerated visuals to the Compose UI.

The audience is a Compose Multiplatform developer who wants those effects, which in practice means a team building a design-led application with a heavy custom component layer. The homepage is on GitHub Pages, the artefact is published to Maven Central under the group `io.github.fletchmckee.liquid`, and the licence is Apache-2.0. The last push to the repository was on 2026-10-13, and the recorded releases are 1.1.1 from 2026-01-18, 1.1.0 from 2025-12-03 and 1.0.1 from 2025-11-18, so the library is on a 1.1.x line with releases roughly every two months.

Two layers: the content and the effect

The sample implementation in the documentation is short, and its structure is the design.

kotlin
@Composable
fun LiquidScreen(
  modifier: Modifier = Modifier,
  liquidState: LiquidState = rememberLiquidState(),
) = Box(modifier) {
  // Content layer for `liquefiable` source nodes
  ImageBackground(
    Modifier
      .fillMaxSize()
      .liquefiable(liquidState),
  )
  // Control layer for `liquid` effect nodes
  LiquidButton(
    Modifier
      .align(Alignment.TopStart)
      .liquid(liquidState),
  )
}

There are two distinct modifiers and they do different jobs. `liquefiable` marks a node as a content source, so it becomes something the effect layer can sample. `liquid` marks a node as an effect, so it draws using what the sources provide. Both take the same `liquidState`, which is the object that ties the two together, and `rememberLiquidState()` produces it and keeps it across recompositions.

The arrangement in the sample is a `Box`, and the effect button is aligned to the top start of it, so the effect is a sibling of the content rather than a child of it. That is not a stylistic choice. If the effect node were a child of the content node, the content would have to be drawn first and the effect would have to be laid out inside it, and there would be no clean boundary for the sampling to work against. Separating them into a content layer and a control layer is what makes the sampling well defined.

The practical consequence for an adopter is a constraint on layout. Anything you want to refract has to be tagged `liquefiable`, and everything you want to refract with has to be tagged `liquid`, and they have to share a state. A component that is neither, a plain `Text` for instance, is invisible to the effect.

There is also a question the documentation does not answer on this page, which is what happens with several overlapping effect nodes competing for the same sources. The full documentation is linked from the quick start for that kind of detail.

Adding it to a Gradle build

Installation is a dependency declaration, and the documentation gives the exact form.

gradle
repositories {
  mavenCentral()
}

dependencies {
  implementation("io.github.fletchmckee.liquid:liquid:1.1.1")
}

The coordinate is `io.github.fletchmckee.liquid:liquid` at version 1.1.1, and it comes from Maven Central, so there is no special repository to add beyond `mavenCentral()`. The group id follows the GitHub Pages pattern of `io.github.<user>`, which is what the Maven badge in the README points at.

The repository layout shows what a Compose Multiplatform library of this kind contains in practice. There is a `liquid/` directory for the library module, a `core/` directory, a `samples/` directory with separate subdirectories for android, ios, jvm and web, a `build-logic/` directory for the convention plugins, a `benchmark/` directory, `docs/` with a `mkdocs.yml` for the documentation site, a `spotless/` directory for code formatting, a `kotlin-js-store/` directory, and a `compose_stability.conf` file, which is the Compose compiler configuration for stability inference.

The presence of `benchmark/` and `spotless/` is a reasonable signal about the project's priorities. The benchmarks are there because a graphics library should be measured, and Spotless is there because a multiplatform Kotlin codebase drifts in formatting quickly without automation.

One practical note about the version. The sample in the README pins 1.1.1, which matches the most recent recorded release, so a fresh install gets the current line. Because the library is pre-2.0 and the API has been moving between 1.0 and 1.1, pinning the version explicitly rather than using a dynamic range is the safer choice for an application, since the modifier names and the `LiquidState` API are the surface your code actually depends on.

Where the pixels come from, and what that costs

The mechanism is a runtime shader, and understanding what that means sets the boundaries of the library.

A RuntimeShader is a fragment shader supplied at runtime rather than compiled into the application, which is what makes the effect configurable and what lets Compose run the sampling on the GPU. Combined with ModifierNodeElement, which is the mechanism Compose provides for a modifier to own state and participate in the layout and draw phases, you get a node that can both read a texture of what was drawn and draw over it.

The cost of that is a copy. For an effect to sample the content behind it, the content has to be rendered into something the effect can read, which means an offscreen buffer and a second pass. That is a real cost in fill rate, and it is why the library separates the content layer from the effect layer rather than trying to blend them into one pass.

This is also the limitation to be honest about. The sample is of the content that was tagged as a source, at the point in the draw order where that content was rendered. It is not a live texture of the frame. Anything that changes after the source is drawn, an animation inside the source layer, a value that updates a frame later, will not be reflected in the refraction the way a true background sampling would. The documentation on this page does not describe the update semantics, so the exact behaviour under animation is something to check in the full documentation and the samples.

The second limitation is platform support. A runtime shader is a Skia-level feature, and Compose Multiplatform's rendering across Android, iOS, JVM and web does not have one uniform implementation of everything Compose for Desktop does. The library ships samples for android, ios, jvm and web, which tells you the intent, but a sample existing and the effect working identically on every target are different claims. The web target in particular is the one to check first, because browser rendering goes through a different path.

A third point is about scope rather than correctness. This is an effect library, not a rendering framework. It does not give you the ability to composite arbitrary shaders into your UI, it gives you a specific set of glass and lens effects built on a specific two-layer model.

The effect vocabulary started elsewhere

The acknowledgements section is unusually specific about where each piece of the design came from, and reading it tells you what this library is and is not.

The Haze library by Chris Banes was a large source of inspiration, particularly for the use of content and effect `Modifier` nodes. That is the same two-layer model, and the fact that the same author built a competing or complementary library for the same problem is itself informative: the two-layer content and effect approach is the established pattern, not a Liquid invention.

The original liquid lens effect was inspired by the ShaderToy user 4eckme and their Liquid Glass example. The current spherical liquid lens effect was inspired by the GitHub user Kyant0 and their own AndroidLiquidGlass library. And the dispersion effect was inspired by the ShaderToy user PuZo with their chromatic aberration example.

So the three headline effects have three different provenances, and two of them are ShaderToy pieces of art rather than a research result. That is not a criticism, because ShaderToy is where this kind of shader work gets done, but it does set an expectation. Liquid is a well-executed port of known visual ideas into a Compose idiom, with a clean modifier model, not the home of new shader research.

The credits also include the photographers whose images the samples use, and they are worth noting for a different reason. Several sample images are webp files in the shared sample resources, which tells you the library ships actual photographic content for the effects to work on. A frosted glass effect over a flat colour looks like nothing, so the samples need real images, and the project had to find images it could ship.

The author is Colin McKee, whose name appears in the Apache-2.0 licence text, and the project is version 1.1.1 with the Apache 2.0 terms spelled out in full in the README.

Editorial conclusion

Liquid suits a Compose Multiplatform team that needs glass, lens or distortion effects and has accepted that the effect layer reads from a snapshot of what is behind it rather than from a live texture. It does not suit a project that needs to compose those effects into a single pass with the rest of the frame, because the design separates the content layer from the effect layer. Verify first that the target platform in your build actually exposes the runtime shader support the effect relies on, that the sample applications build for your targets, and that a version bump does not change the modifier names your screens already use, since the library is on a 1.1.x line and the API is still moving.

Frequently asked questions

What does the Liquid library do for Compose Multiplatform?

It lets modifier nodes sample and manipulate the pixels behind them. You tag content nodes as liquefiable and effect nodes as liquid, sharing a LiquidState, which makes effects like dynamic frosted glass, lenses and other distortions possible inside a Compose UI.

How do I add Liquid to a Gradle build?

Add mavenCentral() to your repositories and declare implementation("io.github.fletchmckee.liquid:liquid:1.1.1") in your dependencies. The artefact is published to Maven Central under the group io.github.fletchmckee.liquid.

What is the difference between the liquefiable and liquid modifiers?

The liquefiable modifier marks a node as content that effects can sample, and the liquid modifier marks a node as an effect that draws using those samples. Both take the same LiquidState, and the effect node has to be a sibling of the content rather than a child of it.

Which platforms does Liquid support?

The repository ships sample applications under samples/android, samples/ios, samples/jvm and samples/web, so the intent covers Android, iOS, desktop JVM and web. Because Compose Multiplatform's rendering is not uniform across those targets, it is worth building the samples for your own targets before depending on an effect.

What licence is Liquid released under?

Apache-2.0, with the full licence text reproduced in the project documentation and a LICENSE file at the root of the repository. The package is published to Maven Central as io.github.fletchmckee.liquid:liquid.

Official sources

  1. FletchMcKee/liquid on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
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/fletchmckee-liquid.svg)](https://hysenlabs.com/projects/fletchmckee-liquid)

Was this useful?