---
title: "Exploring Modifier.Node for creating custom Modifiers in Jetpack Compose"
description: "In this article, you will learn how to create custom modifiers using the three primary APIs, Modifier.then(), Modifier.composed(), and Modifier.Node"
language: "en"
publishedAt: "2025-09-30T01:47:21Z"
updatedAt: "2025-09-30T01:47:21Z"
authors:
  - name: "Jaewoong Eum"
    url: "https://www.revenuecat.com/blog/author/jaewoong-eum"
category: "Engineering"
categoryUrl: "https://www.revenuecat.com/blog/engineering"
readingTime: 6
canonical: "https://www.revenuecat.com/blog/engineering/compose-custom-modifier"
---

# Exploring Modifier.Node for creating custom Modifiers in Jetpack Compose

In this article, you will learn how to create custom modifiers using the three primary APIs, Modifier.then(), Modifier.composed(), and Modifier.Node

## Table of contents

- [Modifier.then](#modifierthen)
- [Modifier.composed](#modifiercomposed)
- [Modifier.Node: The state and logic](#modifiernode-the-state-and-logic)
  - [Modifier.Node](#modifiernode)
  - [The Modifier Factory](#the-modifier-factory)
- [Conclusion](#conclusion)

In modern Android development, Jetpack Compose has become a modern UI toolkit, not only for building Android UIs but also for supporting Kotlin Multiplatform projects. Its ecosystem continues to grow rapidly, making it an essential part of today’s Android development.

At the core of Compose lies the `Modifier`. A modifier is more than just a way to apply styles and themes, it’s a design pattern that enables components to remain independent while still being easily customizable. By creating custom modifiers, you can encapsulate behavior once and reuse it throughout your app, making your UI code both cleaner and more flexible.

In this article, we’ll explore how to create custom modifiers with primary three different APIs, `Modifier.then()`, `Modifier.composed()`, and `Modifier.Node`, and demonstrates how [RevenueCat’s Android SDK](https://github.com/RevenueCat/purchases-android) leverages `Modifier.Node` to deliver better UI performance.

## Modifier.then

This is the easiest way to create a custom modifier by using the `Modifier.then()` function, letting you combine multiple modifiers through chaining and return the final result, as shown in the example below:

```kotlin
@Composable
fun Modifier.composableModifier(): Modifier {
    val color = LocalContentColor.current.copy(alpha = 0.5f)
    return this then Modifier.background(color)
}

@Composable
fun MyComposable() {
    val composedModifier = Modifier.composableModifier() \/\/ Cannot be extracted any higher
}
```

The `infix fun Modifier.then(other: Modifier)` is the fundamental operator responsible for chaining modifiers in Jetpack Compose. It does not create a flat list but rather a recursive, two-element data structure called `CombinedModifier`. This structure functions as a form of cons list or linked list, optimized for the specific directional traversal patterns required by the Compose UI toolkit.

Let’s say there’s a simple modifier, that allows developers to chain modifiers declaratively to build up the properties of a UI element:

```kotlin
Modifier
    .padding(16.dp)      \/\/ First (outer)
    .background(Color.Blue) \/\/ Second
    .clickable { }       \/\/ Third (inner)
```

One might assume this creates a `List<Modifier.Element>`. However, this is not the case. The `Modifier.then()` function, which is implicitly called by each subsequent modifier in the chain, constructs a nested, tree-like structure. This design is a deliberate and crucial architectural choice for performance and correctness.

If you look into the internal codes of the `Modifier.then()` function, you’ll find it internally creates a new `CombinedModifier` instance.

```kotlin

infix fun then(other: Modifier): Modifier =
    if (other === Modifier) this else CombinedModifier(this, other)
        
 class CombinedModifier(internal val outer: Modifier, internal val inner: Modifier) : Modifier {
    override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
        inner.foldIn(outer.foldIn(initial, operation), operation)

    override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R =
        outer.foldOut(inner.foldOut(initial, operation), operation)

    override fun any(predicate: (Modifier.Element) -> Boolean): Boolean =
        outer.any(predicate) || inner.any(predicate)

    override fun all(predicate: (Modifier.Element) -> Boolean): Boolean =
        outer.all(predicate) && inner.all(predicate)

    override fun equals(other: Any?): Boolean =
        other is CombinedModifier && outer == other.outer && inner == other.inner

    override fun hashCode(): Int = outer.hashCode() + 31 * inner.hashCode()

    override fun toString() =
        "[" +
            foldIn("") { acc, element ->
                if (acc.isEmpty()) element.toString() else "$acc, $element"
            } +
            "]"
}
```

Its sole purpose is to take two modifiers, the existing chain (`this`) and a new one (`other`), and join them using the `CombinedModifier` class. The `CombinedModifier` is not a collection; it is a simple, two-element container. It acts as a node in a linked list.

```kotlin
class CombinedModifier(internal val outer: Modifier, internal val inner: Modifier) : Modifier
```

- `outer`: This holds the modifier that came *before* in the chain.
- `inner`: This holds the modifier that was just added.
When you write `Modifier.padding(8.dp).background(Color.Red)`, the resulting structure is: `CombinedModifier(outer = CombinedModifier(outer = padding, inner = background), inner = clickable)`

This creates a left-associative, recursive structure that looks like this: `((padding then background) then clickable)`

This “linked list” structure is the key to understanding how modifiers are processed. The creative `CombinedModifier` structure is realized through its traversal methods. A standard list has one way to iterate (from start to end). `CombinedModifier` has two, with opposite directions, which are important for the different phases of the UI pipeline.

So, ultimately, `Modifier.then` is the simple yet optimized function that enables the entire declarative and chainable modifier system, forming the backbone of UI customization in Jetpack Compose.

## Modifier.composed

Another available API is `Modifier.composed()`, which lets you create a custom modifier that can leverage composable functions inside the lambda parameter.

```kotlin
fun Modifier.myStatefulModifier() = composed {
    val myState by remember { mutableStateOf(0f) }
    \/\/ ... use myState
    Modifier.drawBehind { \/* ... *\/ }
}
```

Internally, it behaves much like `Modifier.then`, since it creates a new instance of `ComposedModifier` and combines it with the existing modifier using `Modifier.then`, as shown below:

```kotlin
fun Modifier.composed(
    inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
    factory: @Composable Modifier.() -> Modifier,
): Modifier = this.then(ComposedModifier(inspectorInfo, factory))
```

While `Modifier.composed` makes it easy to create custom modifiers, this API is **no longer recommended** because of the performance issues it introduces. The method allows you to invoke `@Composable` functions, such as `remember`, when creating a modifier instance. This is useful for attaching an instance-specific state, which lets the same modifier object be reused safely in multiple places.

However, if you use `Modifier.composed` without actually calling any `@Composable` functions, it becomes unnecessary overhead. In this case, the modifier is no longer considered “*skippable”*, which forces the runtime to perform additional work during recomposition. As a result, every recomposition re-executes the modifier chain to update state, leading to degraded performance.

## Modifier.Node: The state and logic

`Modifier.Node` has been introduced since Compose UI version 1.3.0, and now this is the most recommended API for creating a custom Modifier against `Modifier.composed`. While the `Modifier.composed` factory provided an early mechanism for creating custom, stateful modifiers, it came with a significant performance cost: the creation of a new subcomposition for every modifier instance. This overhead could lead to degraded performance, especially in dynamic UIs like lists.

The `Modifier.Node` system was introduced to solve this problem directly. It is a new, lower-level API that allows developers to create stateful modifiers that are lightweight, lifecycle-aware, and integrated into Compose’s core rendering pipeline, without the overhead of subcomposition.

### Modifier.Node

The `Modifier.Node` implementation is the heart of your custom modifier. It is a stateful, mutable class that holds the modifier’s properties and implements its core logic. Critically, a `Modifier.Node` instance can survive across multiple recompositions and can even be reused if its configuration remains the same.

```kotlin
\/\/ The Node: Holds state (color) and logic (drawCircle)
private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() {
    override fun ContentDrawScope.draw() {
        \/\/ Implements the drawing logic
        drawCircle(color)
    }
}
```

The `Element` has two primary responsibilities:

1. create(): Instantiates the `Modifier.Node` the very first time the modifier is applied.

2. update(node: CircleNode): This is the key to performance. On subsequent recompositions, if the `Element` has changed (determined by its `equals` method), the `update` function is called on the existing `Node` instance. This allows you to update the `Node's` state without the expensive cost of creating a new object.

**Note:** It is critical that your `ModifierNodeElement` correctly implements `equals` and `hashCode`. Using a data class is the easiest way to ensure this. Without a correct implementation, `update` may be called unnecessarily, defeating the performance benefits.

### The Modifier Factory

This is the clean, public API surface function that developers will use. It simply uses the `then` operator to chain the new `ModifierNodeElement` onto the existing modifier chain.

```kotlin
\/\/ The Factory: The clean public API
fun Modifier.circle(color: Color): Modifier = this.then(CircleElement(color))
```

You can now use your custom modifier just like any other built-in modifier function. By separating the stateless factory (`Element`) from the stateful worker (`Node`), it minimizes allocations and maximizes object reuse. Its rich set of specialized node types and lifecycle-aware APIs provides developers with direct, safe, and efficient access to every phase of the UI pipeline.

[RevenueCat’s Android SDK](https://www.revenuecat.com/docs/getting-started/installation/android) also leverages Modifier.Node to achieve better UI performance. We recently migrated from the older Modifier.composed() API to `Modifier.Node` when implementing the placeholder feature. You can check out [Placeholder.kt file](https://github.com/RevenueCat/purchases-android/blob/main/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/composables/Placeholder.kt) to see the full changes and how this works in a real-world example.

## Conclusion

In this article, you’ve explored how to create custom modifiers using the three primary APIs, `Modifier.then()`, `Modifier.composed()`, and `Modifier.Node`, and learned how the [RevenueCat SDK](https://www.revenuecat.com/docs/getting-started/installation/android) leverages `Modifier.Node` to improve UI performance. Understanding the trade-offs between these approaches is key: while `Modifier.then()` is simple and lightweight, `Modifier.composed()` introduces performance costs when misused, and `Modifier.Node` offers a more modern, efficient way to build custom behaviors. By applying these patterns thoughtfully, you can create modifiers that are not only reusable and expressive but also optimized for performance, making your Compose UIs more stable.

As always, happy coding!

— [Jaewoong](https://github.com/skydoves)

---

## Related posts

- [Mark your models as stable with the Compose runtime annotation library](https://www.revenuecat.com/blog/engineering/compose-runtime-annotation)
- [remember vs rememberSaveable: deep dive into state management and recomposition in Jetpack Compose](https://www.revenuecat.com/blog/engineering/remember-vs-remembersaveable)
- [Turn Your App into Revenue: Building Paywalls in Android With Jetpack Compose](https://www.revenuecat.com/blog/engineering/build-paywalls-compose)
